From a6ff8859b6bec6853969d8c93d9ced273fc1ade4 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 10:03:50 +0200 Subject: [PATCH 01/41] add folder for STLIB --- stlib/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 stlib/__init__.py diff --git a/stlib/__init__.py b/stlib/__init__.py new file mode 100644 index 000000000..34e9c316c --- /dev/null +++ b/stlib/__init__.py @@ -0,0 +1,4 @@ +"""SPLIB is now relocated at: https://github.com/SofaDefrost/STLIB, please clone and install the plugin to use it""" +raise Exception("SPLIB is now relocated at: https://github.com/SofaDefrost/STLIB, please clone and install the plugin to use it") + + From 60352a4c0534be2bc1d10c2ed7c2a2db9eda2c67 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 10:14:51 +0200 Subject: [PATCH 02/41] Add reusable methods --- splib/Testing.py | 122 +++++++++++++++++++++++++ splib/__init__.py | 4 +- splib/core/__init__.py | 1 + splib/core/enum_types.py | 34 +++++++ splib/core/node_wrapper.py | 73 +++++++++++++++ splib/core/utils.py | 36 ++++++++ splib/mechanics/__init__.py | 1 + splib/mechanics/collision_model.py | 18 ++++ splib/mechanics/fix_points.py | 30 +++++++ splib/mechanics/hyperelasticity.py | 14 +++ splib/mechanics/linear_elasticity.py | 26 ++++++ splib/mechanics/mass.py | 19 ++++ splib/simulation/__init__.py | 1 + splib/simulation/headers.py | 128 +++++++++++++++++++++++++++ splib/simulation/linear_solvers.py | 31 +++++++ splib/simulation/ode_solvers.py | 14 +++ splib/topology/__init__.py | 1 + splib/topology/dynamic.py | 67 ++++++++++++++ splib/topology/loader.py | 22 +++++ splib/topology/static.py | 7 ++ 20 files changed, 646 insertions(+), 3 deletions(-) create mode 100644 splib/Testing.py create mode 100644 splib/core/__init__.py create mode 100644 splib/core/enum_types.py create mode 100644 splib/core/node_wrapper.py create mode 100644 splib/core/utils.py create mode 100644 splib/mechanics/__init__.py create mode 100644 splib/mechanics/collision_model.py create mode 100644 splib/mechanics/fix_points.py create mode 100644 splib/mechanics/hyperelasticity.py create mode 100644 splib/mechanics/linear_elasticity.py create mode 100644 splib/mechanics/mass.py create mode 100644 splib/simulation/__init__.py create mode 100644 splib/simulation/headers.py create mode 100644 splib/simulation/linear_solvers.py create mode 100644 splib/simulation/ode_solvers.py create mode 100644 splib/topology/__init__.py create mode 100644 splib/topology/dynamic.py create mode 100644 splib/topology/loader.py create mode 100644 splib/topology/static.py diff --git a/splib/Testing.py b/splib/Testing.py new file mode 100644 index 000000000..9347f8c2d --- /dev/null +++ b/splib/Testing.py @@ -0,0 +1,122 @@ +from splib.topology.dynamic import * +from splib.simulation.headers import * +from splib.simulation.ode_solvers import * +from splib.simulation.linear_solvers import * +from splib.mechanics.linear_elasticity import * +from splib.mechanics.mass import * +from splib.mechanics.fix_points import * +from splib.topology.loader import * +from splib.core.node_wrapper import * + +from splib.core.node_wrapper import ChildWrapper, ObjectWrapper + + +class displayNode(): + def __init__(self,_level=0): + self.prefix = "" + for i in range(_level): + self.prefix += "| " + + def addObject(self,type:str,**kwargs): + print(self.prefix + type + " with " + str(kwargs)) + + def addChild(self,name:str): + print(self.prefix + "-> Node : " + name) + return displayNode(len(self.prefix) + 1) + +class exportScene(): + def __init__(self,name="rootNode"): + self.name = name + + def addObject(self,type:str,**kwargs): + suffix = "" + for i in kwargs: + suffix += "," + str(i) + "=\"" + str(kwargs[i]) + "\"" + print(self.name+".addObject(\"" + type + "\"" + suffix + ")") + + def addChild(self,name:str): + print(name + '=' + self.name+".addChild(\"" + name + "\")") + setattr(self,name,exportScene(name)) + return getattr(self,name) + + def __setattr__(self, key, value): + if(not(key == "name")): + print(self.__dict__["name"] + "." + key + " = " + str(value)) + self.__dict__[key] = value + else: + self.__dict__[key] = value + + +@PrefabSimulation +def createScene(rootNode): + rootNode.dt = 0.03 + rootNode.gravity = [0,-9.81,0] + + setupLagrangianCollision(rootNode,requiredPlugins={"pluginName":['Sofa.Component.Constraint.Projective', + 'Sofa.Component.Engine.Select', + 'Sofa.Component.LinearSolver.Direct', + 'Sofa.Component.Mass', + 'Sofa.Component.ODESolver.Backward', + 'Sofa.Component.SolidMechanics.FEM.Elastic', + 'Sofa.Component.StateContainer', + 'Sofa.Component.Topology.Container.Grid', + 'Sofa.Component.IO.Mesh', + 'Sofa.Component.LinearSolver.Direct', + 'Sofa.Component.Topology.Container.Dynamic', + 'Sofa.Component.Visual']}) + # + # + # ## TODO : Being able to call "childNode.addAnything" by using the __getattr__ method + Liver0=rootNode.addChild("Liver0") + Liver0.addObject("EulerImplicitSolver",name="ODESolver",rayleighStiffness="0",rayleighMass="0") + Liver0.addObject("SparseLDLSolver",name="LinearSolver",template="CompressedRowSparseMatrixMat3x3",parallelInverseProduct="False") + Liver0.addObject("MeshGmshLoader",name="meshLoader",filename="mesh/liver.msh") + Liver0.addObject("TetrahedronSetTopologyModifier",name="modifier") + Liver0.addObject("TetrahedronSetTopologyContainer",name="container",src="@meshLoader") + Liver0.addObject("TetrahedronSetGeometryAlgorithms",name="algorithms") + Liver0.addObject("MechanicalObject",name="mstate",template="Vec3d") + Liver0.addObject("LinearSolverConstraintCorrection",name="constraintCorrection") + Liver0.addObject("TetrahedronFEMForceField",name="constitutiveLaw",youngModulus="3000",poissonRatio="0.3",method="large") + Liver0.addObject("MeshMatrixMass",name="mass",massDensity="1") + Liver0.addObject("BoxROI",name="fixedBoxROI",box="0 3 0 2 5 2") + Liver0.addObject("FixedProjectiveConstraint",name="fixedConstraints",indices="@fixedBoxROI.indices") + Visu=Liver0.addChild("Visu") + Visu.addObject("TriangleSetTopologyModifier",name="modifier") + Visu.addObject("TriangleSetTopologyContainer",name="container") + Visu.addObject("TriangleSetGeometryAlgorithms",name="algorithms") + Visu.addObject("Tetra2TriangleTopologicalMapping",name="TopologicalMapping",input="@../container",output="@container") + Visu.addObject("OglModel",name="OglModel",topology="@container",color="[1.0, 0.2, 0.8]") + Visu.addObject("IdentityMapping",name="Mapping",isMechanical="False") + + + SimulatedLiver1 = rootNode.addChild("Liver1") + addImplicitODE(SimulatedLiver1) + addLinearSolver(SimulatedLiver1,iterative=False, template="CompressedRowSparseMatrixMat3x3") + loadMesh(SimulatedLiver1,filename="mesh/liver.msh") + addDynamicTopology(SimulatedLiver1,type=ElementType.TETRA,source="@meshLoader") + SimulatedLiver1.addObject("MechanicalObject",name="mstate", template='Vec3d') + SimulatedLiver1.addObject("LinearSolverConstraintCorrection",name="constraintCorrection") + addLinearElasticity(SimulatedLiver1,ElementType.TETRA, poissonRatio="0.3", youngModulus="3000", method='large') + addMass(SimulatedLiver1,template='Vec3d',massDensity="2") + addFixation(SimulatedLiver1,ConstraintType.PROJECTIVE,boxROIs=[0, 3, 0, 2, 5, 2]) + + SimulatedLiverVisu = SimulatedLiver1.addChild("Visu") + addDynamicTopology(SimulatedLiverVisu,ElementType.TRIANGLES) + SimulatedLiverVisu.addObject("Tetra2TriangleTopologicalMapping", name="TopologicalMapping", input="@../container", output="@container") + SimulatedLiverVisu.addObject("OglModel", name="OglModel", topology="@container",color=[1.0,0.2,0.8]) + SimulatedLiverVisu.addObject("IdentityMapping",name="Mapping",isMechanical=False) + + + return rootNode + + + +if __name__ == "__main__": + Node = exportScene() + createScene(Node) + + + + + + diff --git a/splib/__init__.py b/splib/__init__.py index 34e9c316c..a04a5119e 100644 --- a/splib/__init__.py +++ b/splib/__init__.py @@ -1,4 +1,2 @@ -"""SPLIB is now relocated at: https://github.com/SofaDefrost/STLIB, please clone and install the plugin to use it""" -raise Exception("SPLIB is now relocated at: https://github.com/SofaDefrost/STLIB, please clone and install the plugin to use it") - +__all__ = ["core","topology", "simulation","modeler","mechanics","Testing"] diff --git a/splib/core/__init__.py b/splib/core/__init__.py new file mode 100644 index 000000000..78deb3fad --- /dev/null +++ b/splib/core/__init__.py @@ -0,0 +1 @@ +__all__ = ["node_wrapper", "utils","enum_types"] diff --git a/splib/core/enum_types.py b/splib/core/enum_types.py new file mode 100644 index 000000000..dc1b5e864 --- /dev/null +++ b/splib/core/enum_types.py @@ -0,0 +1,34 @@ +from enum import Enum + +class ConstitutiveLaw(Enum): + LINEAR_COROT = 1 + HYPERELASTIC = 2 + +class ODEType(Enum): + EXPLICIT = 1 + IMPLICIT = 2 + +class SolverType(Enum): + DIRECT = 1 + ITERATIVE = 2 + +class MappingType(Enum): + BARYCENTRIC = 1 + IDENTITY = 2 + RIGID = 3 + + +class ConstraintType(Enum): + PROJECTIVE = 1 + WEAK = 2 + LAGRANGIAN = 3 + + + +class ElementType(Enum): + POINTS = 1 + EDGES = 2 + TRIANGLES = 3 + QUAD = 4 + TETRA = 5 + HEXA = 6 diff --git a/splib/core/node_wrapper.py b/splib/core/node_wrapper.py new file mode 100644 index 000000000..7646b9e84 --- /dev/null +++ b/splib/core/node_wrapper.py @@ -0,0 +1,73 @@ +from functools import wraps +from splib.core.utils import defaultValueType +# The two classes are not merged because one could want to use a ReusableMethod +# (enabling to pass dictionary fixing params) without wanting to use a full PrefabSimulation + + +class BaseWrapper(object): + + def __init__(self,node): + self.node = node + + def __getattr__(self, item): + return getattr(self.node,item) + + +class ObjectWrapper(BaseWrapper): + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + + def addObject(self,*args, **kwargs): + parameters = {} + # Expand any parameter that has been set by the user in a custom dictionary + # and having the same key as the component name + if "name" in kwargs: + parameters["name"] = kwargs["name"] + if kwargs["name"] in kwargs: + if isinstance(kwargs[kwargs["name"]], dict): + parameters = {**parameters, **kwargs[kwargs["name"]]} + else: + print("[Warning] You are passing a keyword arg with the same name as one obj without it being a Dict, it will not be used. ") + + # Add all the parameters from kwargs that are not dictionary + for param in kwargs: + if param in parameters: + if not(param == "name"): + print("[Warning] You are redefining the parameter '"+ param + "' of object " + str(args[0])) + elif not(isinstance(kwargs[param], dict)) and not(isinstance(kwargs[param],defaultValueType)): + parameters = {**parameters,param:kwargs[param]} + + return self.node.addObject(*args,**parameters) + + + + +class ChildWrapper(ObjectWrapper): + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + + # This method enforces that the object that is created by the addChild method keeps the prefab type + def addChild(self,*args, **kwargs): + child = self.node.addChild(*args,**kwargs) + returnObject = self.__new__(type(self)) + returnObject.__init__(child) + return returnObject + + + + +def ReusableMethod(method): + @wraps(method) + def wrapper(*args, **kwargs): + + node = args[0] + # We don't want to wrap an object that is already wrapped + # It wouldn't break anything but nodes might get wrapped and wrapped again multiplying redirections... + if( not isinstance(node,ObjectWrapper) ): + node = ObjectWrapper(node) + + if len(args)>1: + return method(node,*args[1:],**kwargs) + else: + return method(node,**kwargs) + return wrapper diff --git a/splib/core/utils.py b/splib/core/utils.py new file mode 100644 index 000000000..56b91f53c --- /dev/null +++ b/splib/core/utils.py @@ -0,0 +1,36 @@ +from typing import List, Callable, Tuple, Dict +from functools import wraps + +class defaultValueType(): + def __init__(self): + pass + +DEFAULT_VALUE = defaultValueType() + +def isDefault(obj): + return isinstance(obj,defaultValueType) + + +def getParameterSet(name : str,parameterSet : Dict) -> Dict: + if name in parameterSet: + if isinstance(parameterSet[name], dict): + return parameterSet[name] + return {} + + +def MapKeywordArg(objectName,*argumentMaps): + def MapArg(method): + @wraps(method) + def wrapper(*args, **kwargs): + containerParams = getParameterSet(objectName,kwargs) + for argMap in argumentMaps: + if (argMap[0] in kwargs) and not(kwargs[argMap[0]] is None): + containerParams[argMap[1]] = kwargs[argMap[0]] + kwargs[objectName] = containerParams + return method(*args,**kwargs) + return wrapper + return MapArg + + + + diff --git a/splib/mechanics/__init__.py b/splib/mechanics/__init__.py new file mode 100644 index 000000000..3761021a2 --- /dev/null +++ b/splib/mechanics/__init__.py @@ -0,0 +1 @@ +__all__ = ["linear_elasticity","hyperelasticity","fix_points","collision_model","mass"] \ No newline at end of file diff --git a/splib/mechanics/collision_model.py b/splib/mechanics/collision_model.py new file mode 100644 index 000000000..40bf0c1d8 --- /dev/null +++ b/splib/mechanics/collision_model.py @@ -0,0 +1,18 @@ +from splib.core.node_wrapper import ReusableMethod +from splib.core.utils import DEFAULT_VALUE +from splib.core.enum_types import ElementType + + + +@ReusableMethod +def addCollisionModels(node,points=False, edges=False,triangles=False, spheres=False,tetrahedron=False,selfCollision=DEFAULT_VALUE, proximity=DEFAULT_VALUE, group=DEFAULT_VALUE, contactStiffness=DEFAULT_VALUE, contactFriction=DEFAULT_VALUE,spheresRadius=DEFAULT_VALUE,**kwargs): + if(points): + node.addObject("PointCollisionModel",name="PointCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, **kwargs) + if(edges): + node.addObject("LineCollisionModel",name="EdgeCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) + if(triangles): + node.addObject("TriangleCollisionModel",name="TriangleCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) + if(spheres): + node.addObject("SphereCollisionModel",name="SphereCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, radius=spheresRadius, **kwargs) + if(tetrahedron): + node.addObject("TetrahedronCollisionModel",name="TetraCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) diff --git a/splib/mechanics/fix_points.py b/splib/mechanics/fix_points.py new file mode 100644 index 000000000..ddbbd6308 --- /dev/null +++ b/splib/mechanics/fix_points.py @@ -0,0 +1,30 @@ +from splib.core.node_wrapper import ReusableMethod +from splib.core.utils import isDefault, DEFAULT_VALUE +from splib.core.enum_types import ConstraintType +from enum import Enum + + +##box +@ReusableMethod +def addFixation(node,type:ConstraintType,boxROIs=DEFAULT_VALUE, sphereROIs=DEFAULT_VALUE, indices=DEFAULT_VALUE, fixAll=DEFAULT_VALUE,**kwargs): + if (isDefault(indices)): + if(not isDefault(boxROIs)): + node.addObject("BoxROI",name='fixedBoxROI',box=boxROIs,**kwargs) + indices="@fixedBoxROI.indices" + if(not isDefault(sphereROIs)): + node.addObject("SphereROI",name='fixedSphereROI',centers=sphereROIs[0],radii=sphereROIs[1],**kwargs) + indices="@fixedSphereROI.indices" + + match type: + case ConstraintType.WEAK: + node.addObject("FixedWeakConstraint",name="fixedConstraints", indices=indices, fixAll=fixAll, **kwargs) + return + case ConstraintType.PROJECTIVE: + node.addObject("FixedProjectiveConstraint",name="fixedConstraints", indices=indices, fixAll=fixAll, **kwargs) + return + case ConstraintType.LAGRANGIAN: + node.addObject("LagrangianFixedConstraint",name="fixedConstraints", indices=indices, fixAll=fixAll, **kwargs) + return + case _: + print('Contraint type is either ConstraintType.PROJECTIVE, ConstraintType.WEAK or ConstraintType.LAGRANGIAN') + return diff --git a/splib/mechanics/hyperelasticity.py b/splib/mechanics/hyperelasticity.py new file mode 100644 index 000000000..1e79c4206 --- /dev/null +++ b/splib/mechanics/hyperelasticity.py @@ -0,0 +1,14 @@ +from splib.core.node_wrapper import ReusableMethod +from splib.core.utils import DEFAULT_VALUE +from splib.core.enum_types import ElementType + + +@ReusableMethod +def addHyperelasticity(node,elem:ElementType,materialName=DEFAULT_VALUE, parameterSet=DEFAULT_VALUE, matrixRegularization=DEFAULT_VALUE,**kwargs): + match elem: + case ElementType.TETRA: + node.addObject("TetrahedronHyperelasticityFEMForceField",name="constitutiveLaw", materialName=materialName, parameterSet=parameterSet, matrixRegularization=matrixRegularization, **kwargs) + return + case _: + print('Hyperelasticity model only exist for Tetrahedron elements.') + return \ No newline at end of file diff --git a/splib/mechanics/linear_elasticity.py b/splib/mechanics/linear_elasticity.py new file mode 100644 index 000000000..8675afe19 --- /dev/null +++ b/splib/mechanics/linear_elasticity.py @@ -0,0 +1,26 @@ +from splib.core.node_wrapper import ReusableMethod +from splib.core.utils import DEFAULT_VALUE +from splib.core.enum_types import ElementType + + +@ReusableMethod +def addLinearElasticity(node,elem:ElementType,youngModulus=DEFAULT_VALUE, poissonRatio=DEFAULT_VALUE, method=DEFAULT_VALUE,**kwargs): + match elem: + case ElementType.EDGES: + node.addObject("BeamFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method, **kwargs) + return + case ElementType.TRIANGLES: + node.addObject("TriangleFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) + return + case ElementType.QUAD: + node.addObject("QuadBendingFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) + return + case ElementType.TETRA: + node.addObject("TetrahedronFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) + return + case ElementType.HEXA: + node.addObject("HexahedronFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) + return + case _: + print('Linear elasticity is only available for topology of type EDGES, TRIANGLES, QUADS, TETRAHEDRON, HEXAHEDRON') + return \ No newline at end of file diff --git a/splib/mechanics/mass.py b/splib/mechanics/mass.py new file mode 100644 index 000000000..e3fe68fe3 --- /dev/null +++ b/splib/mechanics/mass.py @@ -0,0 +1,19 @@ +from splib.core.node_wrapper import ReusableMethod +from splib.core.utils import defaultValueType, DEFAULT_VALUE, isDefault +from splib.core.enum_types import ElementType + + +@ReusableMethod +def addMass(node,template,totalMass=DEFAULT_VALUE,massDensity=DEFAULT_VALUE,lumping=DEFAULT_VALUE,**kwargs): + if (not isDefault(totalMass)) and (not isDefault(massDensity)) : + print("[warning] You defined the totalMass and the massDensity in the same time, only taking massDensity into account") + kwargs.pop('massDensity') + + if(template=="Rigid3"): + node.addObject("UniformMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) + else: + node.addObject("MeshMatrixMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) + + + + diff --git a/splib/simulation/__init__.py b/splib/simulation/__init__.py new file mode 100644 index 000000000..d59a665ef --- /dev/null +++ b/splib/simulation/__init__.py @@ -0,0 +1 @@ +__all__ = ["headers","linear_solvers","ode_solvers"] diff --git a/splib/simulation/headers.py b/splib/simulation/headers.py new file mode 100644 index 000000000..9734d3062 --- /dev/null +++ b/splib/simulation/headers.py @@ -0,0 +1,128 @@ +from splib.core.node_wrapper import ReusableMethod +from enum import Enum + +from splib.core.utils import DEFAULT_VALUE + + +class CollisionType(Enum): + NONE = 1 + PENALITY = 2 + LAGRANGIAN = 3 + + +@ReusableMethod +def setupDefaultHeader(node, displayFlags = "showVisualModels", backgroundColor=[1,1,1,1], parallelComputing=False,**kwargs): + + node.addObject('VisualStyle', displayFlags=displayFlags) + node.addObject('BackgroundSetting', color=backgroundColor) + + node.addObject("RequiredPlugin", name="requiredPlugins", pluginName=['Sofa.Component.Constraint.Projective', + 'Sofa.Component.Engine.Select', + 'Sofa.Component.LinearSolver.Direct', + 'Sofa.Component.Mass', + 'Sofa.Component.ODESolver.Backward', + 'Sofa.Component.SolidMechanics.FEM.Elastic', + 'Sofa.Component.StateContainer', + 'Sofa.Component.Topology.Container.Grid', + 'Sofa.Component.IO.Mesh', + 'Sofa.Component.LinearSolver.Direct', + 'Sofa.Component.ODESolver.Forward', + 'Sofa.Component.Topology.Container.Dynamic', + 'Sofa.Component.Visual', + ], + **kwargs) + node.addObject('DefaultAnimationLoop',name="animation", parallelODESolving=parallelComputing, **kwargs) + + return node + + +@ReusableMethod +def setupPenalityCollisionHeader(node, displayFlags = "showVisualModels",backgroundColor=[1,1,1,1], stick=False, parallelComputing=False, alarmDistance=DEFAULT_VALUE, contactDistance=DEFAULT_VALUE, **kwargs): + node.addObject('VisualStyle', displayFlags=displayFlags) + node.addObject('BackgroundSetting', color=backgroundColor) + + node.addObject("RequiredPlugin", name="requiredPlugins", pluginName=['Sofa.Component.Constraint.Projective', + 'Sofa.Component.Engine.Select', + 'Sofa.Component.LinearSolver.Direct', + 'Sofa.Component.Mass', + 'Sofa.Component.ODESolver.Backward', + 'Sofa.Component.SolidMechanics.FEM.Elastic', + 'Sofa.Component.StateContainer', + 'Sofa.Component.Topology.Container.Grid', + 'Sofa.Component.IO.Mesh', + 'Sofa.Component.LinearSolver.Direct', + 'Sofa.Component.ODESolver.Forward', + 'Sofa.Component.Topology.Container.Dynamic', + 'Sofa.Component.Visual', + ], + **kwargs) + + parallelPrefix = "" + if(parallelComputing): + parallelPrefix="Parallel" + + node.addObject('DefaultAnimationLoop',name="animation", **kwargs) + node.addObject('CollisionPipeline', name="collisionPipeline", **kwargs) + node.addObject(parallelPrefix+'BruteForceBroadPhase', name="broadPhase", **kwargs) + node.addObject(parallelPrefix+'BVHNarrowPhase', name="narrowPhase", **kwargs) + + if(stick): + node.addObject('CollisionResponse',name="ContactManager", response="BarycentricStickContact",**kwargs) + else: + node.addObject('CollisionResponse',name="ContactManager", response="BarycentricPenalityContact",**kwargs) + node.addObject('LocalMinDistance' ,name="Distance", alarmDistance=alarmDistance, contactDistance=contactDistance, **kwargs) + + return node + + +@ReusableMethod +def setupLagrangianCollision(node, displayFlags = "showVisualModels",backgroundColor=[1,1,1,1], parallelComputing=False, stick=False, alarmDistance=DEFAULT_VALUE, contactDistance=DEFAULT_VALUE, frictionCoef=0.0, tolerance=0.0, maxIterations=100, **kwargs): + node.addObject('VisualStyle', displayFlags=displayFlags) + node.addObject('BackgroundSetting', color=backgroundColor) + + node.addObject("RequiredPlugin", name="requiredPlugins", pluginName=['Sofa.Component.Constraint.Lagrangian', + 'Sofa.Component.Constraint.Projective', + 'Sofa.Component.Engine.Select', + 'Sofa.Component.LinearSolver.Direct', + 'Sofa.Component.Mass', + 'Sofa.Component.ODESolver.Backward', + 'Sofa.Component.SolidMechanics.FEM.Elastic', + 'Sofa.Component.StateContainer', + 'Sofa.Component.Topology.Container.Grid', + 'Sofa.Component.IO.Mesh', + 'Sofa.Component.LinearSolver.Direct', + 'Sofa.Component.ODESolver.Forward', + 'Sofa.Component.Topology.Container.Dynamic', + 'Sofa.Component.Visual', + ], + **kwargs) + + + node.addObject('FreeMotionAnimationLoop',name="animation", + parallelCollisionDetectionAndFreeMotion=parallelComputing, + parallelODESolving=parallelComputing, + **kwargs) + + parallelPrefix = "" + if(parallelComputing): + parallelPrefix="Parallel" + + node.addObject('CollisionPipeline', name="collisionPipeline", + **kwargs) + + node.addObject(parallelPrefix+'BruteForceBroadPhase', name="broadPhase", + **kwargs) + + node.addObject(parallelPrefix+'BVHNarrowPhase', name="narrowPhase", + **kwargs) + + if(stick): + node.addObject('CollisionResponse',name="ContactManager", response="StickContactConstraint", responseParams="tol="+str(tolerance),**kwargs) + else: + node.addObject('CollisionResponse',name="ContactManager", response="FrictionContactConstraint", responseParams="mu="+str(frictionCoef),**kwargs) + + node.addObject('NewProximityIntersection' ,name="Distance", alarmDistance=alarmDistance, contactDistance=contactDistance, **kwargs) + node.addObject('GenericConstraintSolver',name="ConstraintSolver", tolerance=tolerance, maxIterations=maxIterations, multithreading=parallelComputing,**kwargs) + node.addObject("ConstraintAttachButtonSetting") + + return node diff --git a/splib/simulation/linear_solvers.py b/splib/simulation/linear_solvers.py new file mode 100644 index 000000000..b6b90f027 --- /dev/null +++ b/splib/simulation/linear_solvers.py @@ -0,0 +1,31 @@ +from splib.core.node_wrapper import ReusableMethod +from splib.core.utils import * + +@ReusableMethod +def addLinearSolver(node,iterative=False,iterations=DEFAULT_VALUE,tolerance=DEFAULT_VALUE,threshold=DEFAULT_VALUE,template=DEFAULT_VALUE,constantSparsity=False,parallelInverseProduct=DEFAULT_VALUE,**kwargs): + containerParams = getParameterSet("LinearSolver",kwargs) + if iterative: + if not isDefault(iterations): + containerParams["iterations"] = iterations + if not isDefault(tolerance): + containerParams["tolerance"] = tolerance + if not isDefault(threshold): + containerParams["threshold"] = threshold + else: + if isDefault(template) and not("template" in containerParams): + containerParams["template"] = 'CompressedRowSparseMatrix' + elif not isDefault(template) : + containerParams["template"] = template + kwargs["LinearSolver"] = containerParams + + if(constantSparsity): + node.addObject("ConstantSparsityPatternSystem",name='LinearSystem',**kwargs) + kwargs["LinearSolver"]["template"] = 'CompressedRowSparseMatrix' + kwargs["LinearSolver"]["linearSystem"]="@LinearSystem" + + + + if iterative: + node.addObject('CGLinearSolver', name='LinearSolver', parallelInverseProduct=parallelInverseProduct, **kwargs) + else: + node.addObject('SparseLDLSolver', name='LinearSolver', parallelInverseProduct=parallelInverseProduct, **kwargs) \ No newline at end of file diff --git a/splib/simulation/ode_solvers.py b/splib/simulation/ode_solvers.py new file mode 100644 index 000000000..eed57bab8 --- /dev/null +++ b/splib/simulation/ode_solvers.py @@ -0,0 +1,14 @@ +from splib.core.node_wrapper import ReusableMethod + +@ReusableMethod +def addImplicitODE(node,static=False,**kwargs): + if( not(static) ): + node.addObject("EulerImplicitSolver",name="ODESolver",**kwargs) + else: + node.addObject("StaticSolver",name="ODESolver",**kwargs) + +@ReusableMethod +def addExplicitODE(node,**kwargs): + node.addObject("EulerExplicitSolver",name="ODESolver",**kwargs) + + diff --git a/splib/topology/__init__.py b/splib/topology/__init__.py new file mode 100644 index 000000000..23469f77d --- /dev/null +++ b/splib/topology/__init__.py @@ -0,0 +1 @@ +__all__ = ["static","dynamic","loader"] \ No newline at end of file diff --git a/splib/topology/dynamic.py b/splib/topology/dynamic.py new file mode 100644 index 000000000..b5a829c39 --- /dev/null +++ b/splib/topology/dynamic.py @@ -0,0 +1,67 @@ +from splib.core.node_wrapper import ReusableMethod +from enum import Enum +from splib.core.utils import MapKeywordArg, DEFAULT_VALUE +from splib.core.enum_types import ElementType + + + +@ReusableMethod +def addPointTopology(node,position=DEFAULT_VALUE,source=DEFAULT_VALUE, **kwargs): + node.addObject("PointSetTopologyModifier", name="modifier", **kwargs) + node.addObject("PointSetTopologyContainer", name="container", src=source, position=position, **kwargs) + # node.addObject("PointSetGeometryAlgorithms", name="algorithms", **kwargs) + +@ReusableMethod +def addEdgeTopology(node,position=DEFAULT_VALUE,edges=DEFAULT_VALUE,source=DEFAULT_VALUE,**kwargs): + node.addObject("EdgeSetTopologyModifier", name="modifier",**kwargs) + node.addObject("EdgeSetTopologyContainer", name="container", src=source, position=position, edges=edges, **kwargs) + # node.addObject("EdgeSetGeometryAlgorithms", name="algorithms",**kwargs) + +@ReusableMethod +def addTriangleTopology(node,position=DEFAULT_VALUE,edges=DEFAULT_VALUE,triangles=DEFAULT_VALUE,source=DEFAULT_VALUE,**kwargs): + node.addObject("TriangleSetTopologyModifier", name="modifier",**kwargs) + node.addObject("TriangleSetTopologyContainer", name="container", src=source, position=position, edges=edges, triangles=triangles, **kwargs) + # node.addObject("TriangleSetGeometryAlgorithms", name="algorithms",**kwargs) + +@ReusableMethod +def addQuadTopology(node,position=DEFAULT_VALUE,edges=DEFAULT_VALUE,quads=DEFAULT_VALUE,source=DEFAULT_VALUE,**kwargs): + node.addObject("QuadSetTopologyModifier", name="modifier",**kwargs) + node.addObject("QuadSetTopologyContainer", name="container", src=source, position=position, edges=edges, quads=quads, **kwargs) + # node.addObject("QuadSetGeometryAlgorithms", name="algorithms",**kwargs) + +@ReusableMethod +def addTetrahedronTopology(node,position=DEFAULT_VALUE,edges=DEFAULT_VALUE,triangles=DEFAULT_VALUE,tetrahedra=DEFAULT_VALUE,source=DEFAULT_VALUE,**kwargs): + node.addObject("TetrahedronSetTopologyModifier", name="modifier",**kwargs) + node.addObject("TetrahedronSetTopologyContainer", name="container", src=source, position=position, edges=edges, triangles=triangles, tetrahedra=tetrahedra, **kwargs) + # node.addObject("TetrahedronSetGeometryAlgorithms", name="algorithms",**kwargs) + +@ReusableMethod +def addHexahedronTopology(node,position=DEFAULT_VALUE,edges=DEFAULT_VALUE,quads=DEFAULT_VALUE,hexahedra=DEFAULT_VALUE,source=DEFAULT_VALUE,**kwargs): + node.addObject("HexahedronSetTopologyModifier", name="modifier",**kwargs) + node.addObject("HexahedronSetTopologyContainer", name="container", src=source, position=position, edges=edges, quads=quads, hexahedra=hexahedra, **kwargs) + # node.addObject("HexahedronSetGeometryAlgorithms", name="algorithms",**kwargs) + +def addDynamicTopology(node,type:ElementType,**kwargs): + + match type: + case ElementType.POINTS: + addPointTopology(node,**kwargs) + return + case ElementType.EDGES: + addEdgeTopology(node,**kwargs) + return + case ElementType.TRIANGLES: + addTriangleTopology(node,**kwargs) + return + case ElementType.QUAD: + addQuadTopology(node,**kwargs) + return + case ElementType.TETRA: + addTetrahedronTopology(node,**kwargs) + return + case ElementType.HEXA: + addHexahedronTopology(node,**kwargs) + return + case _: + print('Topology type should be one of the following : "ElementType.POINTS, ElementType.EDGES, ElementType.TRIANGLES, ElementType.QUAD, ElementType.TETRA, ElementType.HEXA" ') + return diff --git a/splib/topology/loader.py b/splib/topology/loader.py new file mode 100644 index 000000000..f9dd55fbb --- /dev/null +++ b/splib/topology/loader.py @@ -0,0 +1,22 @@ +from splib.core.node_wrapper import ReusableMethod + +@ReusableMethod +def loadMesh(node,filename,**kwargs): + splitedName = filename.split('.') + if len(splitedName) == 1: + print('[Error] : A file name without extension was provided.') + return + + if splitedName[-1] in ['vtk', 'obj', 'stl', 'msh', 'sph']: + if splitedName[-1] == "msh": + return node.addObject("MeshGmshLoader", name="meshLoader",filename=filename, **kwargs) + elif splitedName[-1] == "sph": + return node.addObject("SphereLoader", name="meshLoader",filename=filename, **kwargs) + else: + return node.addObject("Mesh"+splitedName[-1].upper()+"Loader", name="meshLoader",filename=filename, **kwargs) + else: + print('[Error] : File extension ' + splitedName[-1] + ' not recognised.') + + + + diff --git a/splib/topology/static.py b/splib/topology/static.py new file mode 100644 index 000000000..2e49fcf95 --- /dev/null +++ b/splib/topology/static.py @@ -0,0 +1,7 @@ +from splib.core.node_wrapper import ReusableMethod +from splib.core.utils import DEFAULT_VALUE + +@ReusableMethod +def addStaticTopology(node,source=DEFAULT_VALUE,**kwargs): + node.addObject("MeshTopology", name="container", src=source, **kwargs) + From d0241052b82aeed4786fb8a3e8e03cfb6b3025c6 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 10:17:23 +0200 Subject: [PATCH 03/41] Add working files --- stlib/misc/entity.py | 188 +++++++++++++++++++++++++++++++++++++++ stlib/misc/entity2.py | 12 +++ stlib/misc/softrobots.py | 23 +++++ stlib/misc/test-1.py | 53 +++++++++++ stlib/misc/test2.py | 65 ++++++++++++++ 5 files changed, 341 insertions(+) create mode 100644 stlib/misc/entity.py create mode 100644 stlib/misc/entity2.py create mode 100644 stlib/misc/softrobots.py create mode 100644 stlib/misc/test-1.py create mode 100644 stlib/misc/test2.py diff --git a/stlib/misc/entity.py b/stlib/misc/entity.py new file mode 100644 index 000000000..fbf702439 --- /dev/null +++ b/stlib/misc/entity.py @@ -0,0 +1,188 @@ +from typing import Callable, Optional, overload + +import Sofa +import dataclasses + +def addBidule(self): + return self.addChild("Bidule") + +DEFAULT_VALUE = object() + +def NONE(*args, **kwargs): + pass + +def to_dict(o): + if isinstance(o, dict): + return o + if hasattr(o, "to_dict"): + return o.to_dict() + return {} + +@dataclasses.dataclass +class PrefabParameters(object): + name : str = "Prefab" + kwargs : dict = dataclasses.field(default_factory=dict) + + def __getattr__(self, name: str) : + if name == "__getstate__": + getattr(PrefabParameters, "__getstate__") + if name == "__setstate__": + getattr(PrefabParameters, "__setstate__") + + try: + a = self.__getattribute__(name) + except Exception as e: + return NONE + return a + + def to_dict(self): + return dataclasses.asdict(self) + +@dataclasses.dataclass +class VisualModelParameters(PrefabParameters): + name : str = "VisualModel" + + filename : str = "mesh/sphere_02.obj" + + renderer : dict = dataclasses.field(default_factory=dict) + mapping : dict = dataclasses.field(default_factory=dict) + +class VisualModel(Sofa.Core.Node): + + def __init__(self, parent=None, parameters : VisualModelParameters = VisualModelParameters()): + Sofa.Core.Node.__init__(self, name=parameters.name) + + if parent != None: + parent.addChild(self) + + self.addObject("MeshOBJLoader", name="loader", filename=parameters.filename) + self.addRenderer(**to_dict(parameters.renderer) | {"src" : "@loader"} ) + self.addMapping(**to_dict(parameters.mapping) ) + + def addRenderer(self, **kwargs): + self.addObject("OglModel", name="renderer", **kwargs) + + def addMapping(self, **kwargs): + self.addObject("RigidMapping", name="mapping", **kwargs) + +class CollisionModel(Sofa.Core.BasePrefab): + def __init__(self, params, **kwargs): + Sofa.Core.Node.__init__(self, **kwargs) + + class Parameters(object): + enabled : bool = False + +class MechanicalObject(Sofa.Core.Object): + positions : list[float] + + @dataclasses.dataclass + class Parameters(object): + name : str = "MechanicalObject" + + def to_dict(self): + return dataclasses.asdict(self) + + +@dataclasses.dataclass +class SimulationParameters(PrefabParameters): + name : str = "Simulation" + iterations : Optional[int] = None + template: Optional[str] = None + solver : dict = dataclasses.field(default_factory=dict) + integration : dict = dataclasses.field(default_factory=dict) + + def to_dict(self): + return self.asdict() + +class Simulation(Sofa.Core.Node): + solver : Sofa.Core.Object + integration : Sofa.Core.Object + iterations : int + + def __init__(self, parent : Sofa.Core.Node = None, parameters : SimulationParameters = SimulationParameters()): + Sofa.Core.Node.__init__(self, name=parameters.name) + if parent is not None: + parent.addChild(self) + + if parameters.iterations != NONE and "iterations" in parameters.solver: + raise Exception("Cannot set direct attribute and internal hack... ") + + self.addObject("EulerImplicitSolver", name = "integration", **to_dict(parameters.integration)) + self.addObject("CGLinearSolver", name = "solver", iterations=parameters.iterations, **to_dict(parameters.solver)) + + + +#@dataclasses.dataclass +#class Solver(object): +# integrationscheme : str +# numericalsolver : str + +@dataclasses.dataclass +class EntityParameters(PrefabParameters): + name : str = "Entity" + + addSimulation : Callable = Simulation + addCollisionModel : Callable = CollisionModel + addVisualModel : Callable = VisualModel + + #setConstitutiveLaw # : Callable = addBidule + #setBoundaryCondition #: Callable = addBidule + + mechanical : dict = dataclasses.field(default_factory=dict) + collision : CollisionModel.Parameters = CollisionModel.Parameters() + visual : VisualModelParameters = VisualModelParameters() + simulation : SimulationParameters = SimulationParameters() + +class Entity(Sofa.Core.Node): + # A simulated object + simulation : Simulation + visual : VisualModel + collision : CollisionModel + + parameters : EntityParameters + + def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): + Sofa.Core.Node.__init__(self, name=parameters.name) + + if parent is not None: + parent.addChild(self) + + self.parameters = parameters + + self.addMechanicalModel(**parameters.mechanical) + self.addSimulation(parameters=parameters.simulation) + self.addVisualModel(parameters=parameters.visual) + self.addCollisionModel() + + def addMechanicalModel(self, **kwargs): + self.addObject("MechanicalObject", **kwargs) + + def addSimulation(self, **kwargs): + self.parameters.addSimulation(self, **kwargs) + + def addVisualModel(self, **kwargs): + self.parameters.addVisualModel(self, **kwargs) + + def addCollisionModel(self): + pass + +class Rigid(Entity): + def __init__(self, **kwargs): + Entity.__init__(self, **kwargs) + + +class Deformable(Entity): + def __init__(self, **kwargs): + Entity.__init__(self, **kwargs) + +@dataclasses.dataclass +class DeformableEntityParameters(EntityParameters): + addConstitutiveLaw : Callable = lambda x: x + + mass : Optional[float] = None + + def to_dict(self): + return dataclasses.asdict(self) + + + diff --git a/stlib/misc/entity2.py b/stlib/misc/entity2.py new file mode 100644 index 000000000..2baf73b28 --- /dev/null +++ b/stlib/misc/entity2.py @@ -0,0 +1,12 @@ +from typing import Callable, Optional, overload +import Sofa + +class Parameters(object): + addConstitutiveLaw : Callable + mass = 3.4 + +def Deformable(parent, name="Deformable"): + self = parent.addChild(name) + return self + +Deformable.get_parameters = Parameters \ No newline at end of file diff --git a/stlib/misc/softrobots.py b/stlib/misc/softrobots.py new file mode 100644 index 000000000..d8e7871a7 --- /dev/null +++ b/stlib/misc/softrobots.py @@ -0,0 +1,23 @@ +class SoftRobots: + class Cable(Sofa.Core.BasePrefab): + length : float + + def __init__(self,**kwargs): + pass + + def Parameters(object): + lenght : float + +class Trunk(Sofa.Core.BasePrefab): + body : Entity.Deformable + cables : list [SoftRobots.Cable] + + def __init__(self, params): + body = Entity.Deformable() + + for param in range(params.cables): + cables.append(SoftRobots.Cable(body, param)) + + class Parameters(object): + body : Entity.Deformable.Parameters + cables : list[SoftRobots.Cable.Parameters] diff --git a/stlib/misc/test-1.py b/stlib/misc/test-1.py new file mode 100644 index 000000000..289f1ff02 --- /dev/null +++ b/stlib/misc/test-1.py @@ -0,0 +1,53 @@ +from typing import Callable, Optional, overload +import Sofa.Core + + +import entity +import entity2 + +# Monkey patch for demonstration purpose +def newAdd(self, creator, **kwargs): + if callable(creator): + creator(parent=self, **kwargs) +Sofa.Core.Node.add = newAdd + +def createScene(root): + #root.add(entity.Deformable) + #root.addChild(entity2.Deformable(root)) + + params = entity.Deformable.Parameters() + params.name = "Deformable2" + root.add(entity.Deformable, params=auto_load) + + #def addCustomVisual(self, **kwargs): + # Rigid.addVisualModel( mapping={"toto":"in"} ) + + #params = Entity.Parameters() + #params.addVisualModel = addCustomVisual + #root.add(Entity, params) + + #  + #params = Rigid.new_parameters() + #params.mass = 4.5 + #root.add(Entity, params) + #root.add(Entity) + + #params.addVisualModelOverride = addCustomVisual + + ###  + #Entity._addVisualModel = addCustomVisual + #root.add(Entity, params) + + #root.add(Entity.Rigid) + #root.add(Entity.Deformable) + + #root.add(Entity) + #root.add(VisualModel, params) + + #root.add(VisualModel) + + #params = Entity.Deformable.Parameters() + #params.visual = None + #a = root.add(Entity.Deformable, params) + + return root \ No newline at end of file diff --git a/stlib/misc/test2.py b/stlib/misc/test2.py new file mode 100644 index 000000000..4dc12d147 --- /dev/null +++ b/stlib/misc/test2.py @@ -0,0 +1,65 @@ +import Sofa.Core +import copy +import entity +from entity import PrefabParameters, EntityParameters, Entity, Simulation + + +oldAdd=Sofa.Core.Node.addObject +def myAddObject(self : Sofa.Core.Node, tname, **kwargs): + kwargs = copy.copy(kwargs) + previouslen = len(self.objects) + try: + oldAdd(self, tname, **kwargs) + except Exception as e: + target = self + if len(self.objects) != previouslen: + target = list(self.objects)[-1] + Sofa.msg_error(target, str(e)) + +Sofa.Core.Node.addObject = myAddObject + + +def myAdd(self : Sofa.Core.Node, c, params = PrefabParameters(), **kwargs): + def findName(cname, node): + """Compute a working unique name in the node""" + rname = cname + for i in range(0, len(node.children)): + if rname not in node.children: + return rname + rname = cname + str(i+1) + return rname + + for k,v in kwargs.items(): + if hasattr(params, k): + setattr(params, k, v) + + params = copy.copy(params) + if params.name in self.children: + params.name = findName(params.name, self) + + return c(parent = self, parameters=params) +Sofa.Core.Node.add = myAdd + +def createScene(root): + #@optionalkwargs + + #def eulalieAddOde(self, **kwargs): + # self.addObject("EulerExplicitSolver", name="numericalintegration") + # self.addObject("LinearSolver", name="numericalsolver", firstOrder=True) + + params = EntityParameters() + params.simulation.iterations = 10 + params.simulation.integration["rayleighStiffness"] = 2.0 + params.addSimulation = entity.NONE + + params.mechanical["template"] = "Rigid3" + + #params.simulation.integration["rayleightStiffnessXXX"] = 2.0 + + #params.solver.kwargs["numericalintegration"] = { "firstOrder" : True } + + root.add(Entity, params) + root.add(Entity, params) + root.add(Entity, params) + + #root.add(Simulation, name="mySimulation") From 10082bfeaf5132e01866bc9b3f7f2529de4e2b99 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 10:29:32 +0200 Subject: [PATCH 04/41] Created example scenes --- stlib/examples/PrefabScene_beginner.py | 35 ++++++++++++++++++++++ stlib/examples/PrefabScene_expert.py | 0 stlib/examples/PrefabScene_intermediate.py | 0 stlib/examples/helper.py | 23 ++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 stlib/examples/PrefabScene_beginner.py create mode 100644 stlib/examples/PrefabScene_expert.py create mode 100644 stlib/examples/PrefabScene_intermediate.py create mode 100644 stlib/examples/helper.py diff --git a/stlib/examples/PrefabScene_beginner.py b/stlib/examples/PrefabScene_beginner.py new file mode 100644 index 000000000..bbace5a0a --- /dev/null +++ b/stlib/examples/PrefabScene_beginner.py @@ -0,0 +1,35 @@ + +class exportScene(): + def __init__(self,name="rootNode"): + self.name = name + + def addObject(self,type:str,**kwargs): + suffix = "" + for i in kwargs: + suffix += "," + str(i) + "=\"" + str(kwargs[i]) + "\"" + print(self.name+".addObject(\"" + type + "\"" + suffix + ")") + + def addChild(self,name:str): + print(name + '=' + self.name+".addChild(\"" + name + "\")") + setattr(self,name,exportScene(name)) + return getattr(self,name) + + def __setattr__(self, key, value): + if(not(key == "name")): + print(self.__dict__["name"] + "." + key + " = " + str(value)) + self.__dict__[key] = value + else: + self.__dict__[key] = value + + + +def createScene(root): + + + return root + + +if __name__=="__main__": + + + pass \ No newline at end of file diff --git a/stlib/examples/PrefabScene_expert.py b/stlib/examples/PrefabScene_expert.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/examples/PrefabScene_intermediate.py b/stlib/examples/PrefabScene_intermediate.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/examples/helper.py b/stlib/examples/helper.py new file mode 100644 index 000000000..da5dc5205 --- /dev/null +++ b/stlib/examples/helper.py @@ -0,0 +1,23 @@ + + +class exportScene(): + def __init__(self,name="rootNode"): + self.name = name + + def addObject(self,type:str,**kwargs): + suffix = "" + for i in kwargs: + suffix += "," + str(i) + "=\"" + str(kwargs[i]) + "\"" + print(self.name+".addObject(\"" + type + "\"" + suffix + ")") + + def addChild(self,name:str): + print(name + '=' + self.name+".addChild(\"" + name + "\")") + setattr(self,name,exportScene(name)) + return getattr(self,name) + + def __setattr__(self, key, value): + if(not(key == "name")): + print(self.__dict__["name"] + "." + key + " = " + str(value)) + self.__dict__[key] = value + else: + self.__dict__[key] = value From d167479f0ba36ad33366135d558219cf5fbc07d9 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 10:33:01 +0200 Subject: [PATCH 05/41] Fix example --- stlib/examples/PrefabScene_beginner.py | 35 ++++++-------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/stlib/examples/PrefabScene_beginner.py b/stlib/examples/PrefabScene_beginner.py index bbace5a0a..11a16a2df 100644 --- a/stlib/examples/PrefabScene_beginner.py +++ b/stlib/examples/PrefabScene_beginner.py @@ -1,35 +1,14 @@ - -class exportScene(): - def __init__(self,name="rootNode"): - self.name = name - - def addObject(self,type:str,**kwargs): - suffix = "" - for i in kwargs: - suffix += "," + str(i) + "=\"" + str(kwargs[i]) + "\"" - print(self.name+".addObject(\"" + type + "\"" + suffix + ")") - - def addChild(self,name:str): - print(name + '=' + self.name+".addChild(\"" + name + "\")") - setattr(self,name,exportScene(name)) - return getattr(self,name) - - def __setattr__(self, key, value): - if(not(key == "name")): - print(self.__dict__["name"] + "." + key + " = " + str(value)) - self.__dict__[key] = value - else: - self.__dict__[key] = value - +from helper import exportScene def createScene(root): - - return root + + return root if __name__=="__main__": - - - pass \ No newline at end of file + root = exportScene() + createScene(root) + + pass \ No newline at end of file From dd9cc34a371809d5e738dd98721b3bd36af599ac Mon Sep 17 00:00:00 2001 From: hugtalbot Date: Mon, 14 Apr 2025 10:52:31 +0200 Subject: [PATCH 06/41] orga folders --- stlib/__init__.py | 5 +---- stlib/examples/PrefabScene_beginner.py | 4 ++-- stlib/misc/entity2.py | 12 ------------ 3 files changed, 3 insertions(+), 18 deletions(-) delete mode 100644 stlib/misc/entity2.py diff --git a/stlib/__init__.py b/stlib/__init__.py index 34e9c316c..723fc1b46 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -1,4 +1 @@ -"""SPLIB is now relocated at: https://github.com/SofaDefrost/STLIB, please clone and install the plugin to use it""" -raise Exception("SPLIB is now relocated at: https://github.com/SofaDefrost/STLIB, please clone and install the plugin to use it") - - +__all__ = ["misc"] diff --git a/stlib/examples/PrefabScene_beginner.py b/stlib/examples/PrefabScene_beginner.py index 11a16a2df..d28b98e7d 100644 --- a/stlib/examples/PrefabScene_beginner.py +++ b/stlib/examples/PrefabScene_beginner.py @@ -1,8 +1,9 @@ from helper import exportScene +from stlib.misc.entity import Entity def createScene(root): - + Entity.Deformable(Mesh,MechaProperties) return root @@ -10,5 +11,4 @@ def createScene(root): if __name__=="__main__": root = exportScene() createScene(root) - pass \ No newline at end of file diff --git a/stlib/misc/entity2.py b/stlib/misc/entity2.py deleted file mode 100644 index 2baf73b28..000000000 --- a/stlib/misc/entity2.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Callable, Optional, overload -import Sofa - -class Parameters(object): - addConstitutiveLaw : Callable - mass = 3.4 - -def Deformable(parent, name="Deformable"): - self = parent.addChild(name) - return self - -Deformable.get_parameters = Parameters \ No newline at end of file From 7589706e87289f0adb98a5210e512497b1350a21 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 10:55:02 +0200 Subject: [PATCH 07/41] Fix package --- splib/Testing.py | 38 +---------------------------- splib/__init__.py | 2 +- {stlib/examples => splib}/helper.py | 12 +++++++++ 3 files changed, 14 insertions(+), 38 deletions(-) rename {stlib/examples => splib}/helper.py (66%) diff --git a/splib/Testing.py b/splib/Testing.py index 9347f8c2d..fc31093cf 100644 --- a/splib/Testing.py +++ b/splib/Testing.py @@ -9,45 +9,9 @@ from splib.core.node_wrapper import * from splib.core.node_wrapper import ChildWrapper, ObjectWrapper +from splib.helper import exportScene -class displayNode(): - def __init__(self,_level=0): - self.prefix = "" - for i in range(_level): - self.prefix += "| " - - def addObject(self,type:str,**kwargs): - print(self.prefix + type + " with " + str(kwargs)) - - def addChild(self,name:str): - print(self.prefix + "-> Node : " + name) - return displayNode(len(self.prefix) + 1) - -class exportScene(): - def __init__(self,name="rootNode"): - self.name = name - - def addObject(self,type:str,**kwargs): - suffix = "" - for i in kwargs: - suffix += "," + str(i) + "=\"" + str(kwargs[i]) + "\"" - print(self.name+".addObject(\"" + type + "\"" + suffix + ")") - - def addChild(self,name:str): - print(name + '=' + self.name+".addChild(\"" + name + "\")") - setattr(self,name,exportScene(name)) - return getattr(self,name) - - def __setattr__(self, key, value): - if(not(key == "name")): - print(self.__dict__["name"] + "." + key + " = " + str(value)) - self.__dict__[key] = value - else: - self.__dict__[key] = value - - -@PrefabSimulation def createScene(rootNode): rootNode.dt = 0.03 rootNode.gravity = [0,-9.81,0] diff --git a/splib/__init__.py b/splib/__init__.py index a04a5119e..6b380c11a 100644 --- a/splib/__init__.py +++ b/splib/__init__.py @@ -1,2 +1,2 @@ -__all__ = ["core","topology", "simulation","modeler","mechanics","Testing"] +__all__ = ["core","topology", "simulation","modeler","mechanics","Testing","helper"] diff --git a/stlib/examples/helper.py b/splib/helper.py similarity index 66% rename from stlib/examples/helper.py rename to splib/helper.py index da5dc5205..b4881af5f 100644 --- a/stlib/examples/helper.py +++ b/splib/helper.py @@ -1,3 +1,15 @@ +class displayNode(): + def __init__(self,_level=0): + self.prefix = "" + for i in range(_level): + self.prefix += "| " + + def addObject(self,type:str,**kwargs): + print(self.prefix + type + " with " + str(kwargs)) + + def addChild(self,name:str): + print(self.prefix + "-> Node : " + name) + return displayNode(len(self.prefix) + 1) class exportScene(): From 2f844d71d00f8be6765904802347ced5298f2160 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 10:57:28 +0200 Subject: [PATCH 08/41] bootstrap examples --- stlib/examples/PrefabScene_beginner.py | 2 +- stlib/examples/PrefabScene_expert.py | 14 ++++++++++++++ stlib/examples/PrefabScene_intermediate.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/stlib/examples/PrefabScene_beginner.py b/stlib/examples/PrefabScene_beginner.py index d28b98e7d..0bc427d37 100644 --- a/stlib/examples/PrefabScene_beginner.py +++ b/stlib/examples/PrefabScene_beginner.py @@ -1,4 +1,4 @@ -from helper import exportScene +from splib.helper import exportScene from stlib.misc.entity import Entity diff --git a/stlib/examples/PrefabScene_expert.py b/stlib/examples/PrefabScene_expert.py index e69de29bb..0bc427d37 100644 --- a/stlib/examples/PrefabScene_expert.py +++ b/stlib/examples/PrefabScene_expert.py @@ -0,0 +1,14 @@ +from splib.helper import exportScene +from stlib.misc.entity import Entity + + +def createScene(root): + Entity.Deformable(Mesh,MechaProperties) + + return root + + +if __name__=="__main__": + root = exportScene() + createScene(root) + pass \ No newline at end of file diff --git a/stlib/examples/PrefabScene_intermediate.py b/stlib/examples/PrefabScene_intermediate.py index e69de29bb..0bc427d37 100644 --- a/stlib/examples/PrefabScene_intermediate.py +++ b/stlib/examples/PrefabScene_intermediate.py @@ -0,0 +1,14 @@ +from splib.helper import exportScene +from stlib.misc.entity import Entity + + +def createScene(root): + Entity.Deformable(Mesh,MechaProperties) + + return root + + +if __name__=="__main__": + root = exportScene() + createScene(root) + pass \ No newline at end of file From 2c55e7dc4fab0e8b184d75a73dc845e6131f1ff7 Mon Sep 17 00:00:00 2001 From: hugtalbot Date: Mon, 14 Apr 2025 11:28:18 +0200 Subject: [PATCH 09/41] start beginner scene --- stlib/examples/PrefabScene_beginner.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stlib/examples/PrefabScene_beginner.py b/stlib/examples/PrefabScene_beginner.py index 0bc427d37..7cd4312c0 100644 --- a/stlib/examples/PrefabScene_beginner.py +++ b/stlib/examples/PrefabScene_beginner.py @@ -3,8 +3,10 @@ def createScene(root): - Entity.Deformable(Mesh,MechaProperties) - + params = Entity.Deformable.Parameters() + params.name = "Logo" + params.topology.filename="share/mesh/SOFA-logo.obj" + root.add(Entity.Deformable, params=params) return root From 9b9e057fa9edfaa8da64dc710e471e7378bc232e Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 11:31:18 +0200 Subject: [PATCH 10/41] Move around files --- stlib/core/entity.py | 57 ++++++++++++++++++++++++++ stlib/core/parameters.py | 24 +++++++++++ stlib/core/visual_model.py | 32 +++++++++++++++ stlib/examples/PrefabScene_beginner.py | 6 +-- stlib/simulated/deformable.py | 17 ++++++++ stlib/simulated/rigid.py | 0 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 stlib/core/entity.py create mode 100644 stlib/core/parameters.py create mode 100644 stlib/core/visual_model.py create mode 100644 stlib/simulated/deformable.py create mode 100644 stlib/simulated/rigid.py diff --git a/stlib/core/entity.py b/stlib/core/entity.py new file mode 100644 index 000000000..ef756dc8f --- /dev/null +++ b/stlib/core/entity.py @@ -0,0 +1,57 @@ +from stlib.core.parameters import EntityParameters +import dataclasses +from typing import Callable, Optional, overload +import Sofa + + + +class Entity(Sofa.Core.BasePrefab): + + # A simulated object + simulation : Simulation + visual : VisualModel + collision : CollisionModel + + parameters : EntityParameters + + @dataclasses.dataclass + class Parameters(EntityParameters): + name : str = "Entity" + + addSimulation : Callable = Simulation + addCollisionModel : Callable = CollisionModel + addVisualModel : Callable = VisualModel + + #setConstitutiveLaw # : Callable = addBidule + #setBoundaryCondition #: Callable = addBidule + + mechanical : dict = dataclasses.field(default_factory=dict) + collision : CollisionModel.Parameters = CollisionModel.Parameters() + visual : VisualModelParameters = VisualModelParameters() + simulation : SimulationParameters = SimulationParameters() + + + def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): + Sofa.Core.Node.__init__(self, name=parameters.name) + + if parent is not None: + parent.addChild(self) + + self.parameters = parameters + + self.addMechanicalModel(**parameters.mechanical) + self.addSimulation(parameters=parameters.simulation) + self.addVisualModel(parameters=parameters.visual) + self.addCollisionModel() + + def addMechanicalModel(self, **kwargs): + self.addObject("MechanicalObject", **kwargs) + + def addSimulation(self, **kwargs): + self.parameters.addSimulation(self, **kwargs) + + def addVisualModel(self, **kwargs): + self.parameters.addVisualModel(self, **kwargs) + + def addCollisionModel(self): + pass diff --git a/stlib/core/parameters.py b/stlib/core/parameters.py new file mode 100644 index 000000000..b1df61d12 --- /dev/null +++ b/stlib/core/parameters.py @@ -0,0 +1,24 @@ +import dataclasses +from splib.core.utils import DEFAULT_VALUE + +#defaultValueType + +@dataclasses.dataclass +class PrefabParameters(object): + name : str = "Prefab" + kwargs : dict = dataclasses.field(default_factory=dict) + + def __getattr__(self, name: str) : + if name == "__getstate__": + getattr(PrefabParameters, "__getstate__") + if name == "__setstate__": + getattr(PrefabParameters, "__setstate__") + + try: + a = self.__getattribute__(name) + except Exception as e: + return DEFAULT_VALUE + return a + + def to_dict(self): + return dataclasses.asdict(self) diff --git a/stlib/core/visual_model.py b/stlib/core/visual_model.py new file mode 100644 index 000000000..7081c9b2c --- /dev/null +++ b/stlib/core/visual_model.py @@ -0,0 +1,32 @@ +from stlib.core.parameters import EntityParameters +import dataclasses +import Sofa + +class VisualModel(Sofa.Core.BasePrefab): + @dataclasses.dataclass + class Parameters(EntityParameters): + name : str = "VisualModel" + + filename : str = "mesh/sphere_02.obj" + + renderer : dict = dataclasses.field(default_factory=dict) + mapping : dict = dataclasses.field(default_factory=dict) + + + + def __init__(self, parent=None, parameters : VisualModelParameters = VisualModelParameters()): + Sofa.Core.Node.__init__(self, name=parameters.name) + + if parent != None: + parent.addChild(self) + + self.addObject("MeshOBJLoader", name="loader", filename=parameters.filename) + self.addRenderer(**to_dict(parameters.renderer) | {"src" : "@loader"} ) + self.addMapping(**to_dict(parameters.mapping) ) + + def addRenderer(self, **kwargs): + self.addObject("OglModel", name="renderer", **kwargs) + + def addMapping(self, **kwargs): + self.addObject("RigidMapping", name="mapping", **kwargs) + \ No newline at end of file diff --git a/stlib/examples/PrefabScene_beginner.py b/stlib/examples/PrefabScene_beginner.py index 7cd4312c0..a507bd529 100644 --- a/stlib/examples/PrefabScene_beginner.py +++ b/stlib/examples/PrefabScene_beginner.py @@ -1,12 +1,12 @@ from splib.helper import exportScene -from stlib.misc.entity import Entity +from stlib.simulated.deformable import Deformable def createScene(root): - params = Entity.Deformable.Parameters() + params = Deformable.Parameters() params.name = "Logo" params.topology.filename="share/mesh/SOFA-logo.obj" - root.add(Entity.Deformable, params=params) + root.add(Deformable, params=params) return root diff --git a/stlib/simulated/deformable.py b/stlib/simulated/deformable.py new file mode 100644 index 000000000..2c7d43c97 --- /dev/null +++ b/stlib/simulated/deformable.py @@ -0,0 +1,17 @@ +from stlib.core.entity import Entity +from stlib.core.parameters import EntityParameters +import dataclasses + +class Deformable(Entity): + @dataclasses.dataclass + class Parameters(EntityParameters): + addConstitutiveLaw : Callable = lambda x: x + + mass : Optional[float] = None + + def to_dict(self): + return dataclasses.asdict(self) + + def __init__(self, **kwargs): + Entity.__init__(self, **kwargs) + diff --git a/stlib/simulated/rigid.py b/stlib/simulated/rigid.py new file mode 100644 index 000000000..e69de29bb From b989159330158c37036660aaad84ff4a3e247589 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 11:32:57 +0200 Subject: [PATCH 11/41] Move examples into SofaPython3 main example folder --- {stlib/examples => examples/stlib}/PrefabScene_beginner.py | 0 {stlib/examples => examples/stlib}/PrefabScene_expert.py | 0 {stlib/examples => examples/stlib}/PrefabScene_intermediate.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {stlib/examples => examples/stlib}/PrefabScene_beginner.py (100%) rename {stlib/examples => examples/stlib}/PrefabScene_expert.py (100%) rename {stlib/examples => examples/stlib}/PrefabScene_intermediate.py (100%) diff --git a/stlib/examples/PrefabScene_beginner.py b/examples/stlib/PrefabScene_beginner.py similarity index 100% rename from stlib/examples/PrefabScene_beginner.py rename to examples/stlib/PrefabScene_beginner.py diff --git a/stlib/examples/PrefabScene_expert.py b/examples/stlib/PrefabScene_expert.py similarity index 100% rename from stlib/examples/PrefabScene_expert.py rename to examples/stlib/PrefabScene_expert.py diff --git a/stlib/examples/PrefabScene_intermediate.py b/examples/stlib/PrefabScene_intermediate.py similarity index 100% rename from stlib/examples/PrefabScene_intermediate.py rename to examples/stlib/PrefabScene_intermediate.py From b42833b2bd9023a3de3857aac392d1940b6789b1 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 11:35:27 +0200 Subject: [PATCH 12/41] Add unsaved modifications --- stlib/__init__.py | 2 +- stlib/core/parameters.py | 2 +- stlib/simulated/deformable.py | 1 + stlib/simulated/rigid.py | 9 +++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/stlib/__init__.py b/stlib/__init__.py index 723fc1b46..d3aa4e81c 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -1 +1 @@ -__all__ = ["misc"] +__all__ = ["core","simulated"] diff --git a/stlib/core/parameters.py b/stlib/core/parameters.py index b1df61d12..55cd025fe 100644 --- a/stlib/core/parameters.py +++ b/stlib/core/parameters.py @@ -4,7 +4,7 @@ #defaultValueType @dataclasses.dataclass -class PrefabParameters(object): +class EntityParameters(object): name : str = "Prefab" kwargs : dict = dataclasses.field(default_factory=dict) diff --git a/stlib/simulated/deformable.py b/stlib/simulated/deformable.py index 2c7d43c97..933c8806a 100644 --- a/stlib/simulated/deformable.py +++ b/stlib/simulated/deformable.py @@ -1,5 +1,6 @@ from stlib.core.entity import Entity from stlib.core.parameters import EntityParameters + import dataclasses class Deformable(Entity): diff --git a/stlib/simulated/rigid.py b/stlib/simulated/rigid.py index e69de29bb..f830eab39 100644 --- a/stlib/simulated/rigid.py +++ b/stlib/simulated/rigid.py @@ -0,0 +1,9 @@ +from stlib.core.entity import Entity +from stlib.core.parameters import EntityParameters + +import dataclasses + + +class Rigid(Entity): + def __init__(self, **kwargs): + Entity.__init__(self, **kwargs) From fa0bd7429d9feaac04a2d98f4afb7a2759461227 Mon Sep 17 00:00:00 2001 From: EulalieCoevoet Date: Mon, 14 Apr 2025 13:47:30 +0200 Subject: [PATCH 13/41] reoganisation after discussion --- stlib/core/base_entity.py | 9 +++++++++ stlib/core/{parameters.py => base_parameters.py} | 12 +++++++----- stlib/core/base_prefab.py | 10 ++++++++++ stlib/{core/entity.py => entities/__entity__.py} | 3 +-- stlib/entities/__init__.py | 0 .../deformable/__deformable__.py} | 0 stlib/entities/deformable/__init__.py | 0 stlib/entities/deformable/parameters.py | 0 stlib/entities/rigid/__init__.py | 0 .../rigid.py => entities/rigid/__rigid__.py} | 0 stlib/entities/rigid/parameters.py | 0 stlib/{core/visual_model.py => prefabs/visual.py} | 0 stlib/shapes/__init__.py | 0 stlib/shapes/cube.py | 0 stlib/shapes/sphere.py | 0 15 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 stlib/core/base_entity.py rename stlib/core/{parameters.py => base_parameters.py} (67%) create mode 100644 stlib/core/base_prefab.py rename stlib/{core/entity.py => entities/__entity__.py} (97%) create mode 100644 stlib/entities/__init__.py rename stlib/{simulated/deformable.py => entities/deformable/__deformable__.py} (100%) create mode 100644 stlib/entities/deformable/__init__.py create mode 100644 stlib/entities/deformable/parameters.py create mode 100644 stlib/entities/rigid/__init__.py rename stlib/{simulated/rigid.py => entities/rigid/__rigid__.py} (100%) create mode 100644 stlib/entities/rigid/parameters.py rename stlib/{core/visual_model.py => prefabs/visual.py} (100%) create mode 100644 stlib/shapes/__init__.py create mode 100644 stlib/shapes/cube.py create mode 100644 stlib/shapes/sphere.py diff --git a/stlib/core/base_entity.py b/stlib/core/base_entity.py new file mode 100644 index 000000000..a9be8df3a --- /dev/null +++ b/stlib/core/base_entity.py @@ -0,0 +1,9 @@ +import Sofa.Core +from base_parameters import BaseParameters + +class BaseEntity(Sofa.Core.Prefab): + + parameters : BaseParameters + + def __init__(self): + Sofa.Core.Prefab.__init__(self) diff --git a/stlib/core/parameters.py b/stlib/core/base_parameters.py similarity index 67% rename from stlib/core/parameters.py rename to stlib/core/base_parameters.py index 55cd025fe..d1257b4bb 100644 --- a/stlib/core/parameters.py +++ b/stlib/core/base_parameters.py @@ -1,18 +1,20 @@ import dataclasses from splib.core.utils import DEFAULT_VALUE -#defaultValueType +from stlib.shapes import Shape + @dataclasses.dataclass -class EntityParameters(object): - name : str = "Prefab" +class BaseParameters(object): + name : str = "Entity" + shape : Shape = Shape() kwargs : dict = dataclasses.field(default_factory=dict) def __getattr__(self, name: str) : if name == "__getstate__": - getattr(PrefabParameters, "__getstate__") + getattr(BaseParameters, "__getstate__") if name == "__setstate__": - getattr(PrefabParameters, "__setstate__") + getattr(BaseParameters, "__setstate__") try: a = self.__getattribute__(name) diff --git a/stlib/core/base_prefab.py b/stlib/core/base_prefab.py new file mode 100644 index 000000000..067bc5bae --- /dev/null +++ b/stlib/core/base_prefab.py @@ -0,0 +1,10 @@ +import Sofa.Core + + +class BasePrefab(Sofa.Core.Prefab): + """ + A Prefab is a Sofa.Node that assembles a set of components and nodes + """ + + def __init__(self): + Sofa.Core.Prefab.__init__(self) \ No newline at end of file diff --git a/stlib/core/entity.py b/stlib/entities/__entity__.py similarity index 97% rename from stlib/core/entity.py rename to stlib/entities/__entity__.py index ef756dc8f..8d2dd6f55 100644 --- a/stlib/core/entity.py +++ b/stlib/entities/__entity__.py @@ -4,8 +4,7 @@ import Sofa - -class Entity(Sofa.Core.BasePrefab): +class Entity(Sofa.Core.BaseEntity): # A simulated object simulation : Simulation diff --git a/stlib/entities/__init__.py b/stlib/entities/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/simulated/deformable.py b/stlib/entities/deformable/__deformable__.py similarity index 100% rename from stlib/simulated/deformable.py rename to stlib/entities/deformable/__deformable__.py diff --git a/stlib/entities/deformable/__init__.py b/stlib/entities/deformable/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/entities/deformable/parameters.py b/stlib/entities/deformable/parameters.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/entities/rigid/__init__.py b/stlib/entities/rigid/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/simulated/rigid.py b/stlib/entities/rigid/__rigid__.py similarity index 100% rename from stlib/simulated/rigid.py rename to stlib/entities/rigid/__rigid__.py diff --git a/stlib/entities/rigid/parameters.py b/stlib/entities/rigid/parameters.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/core/visual_model.py b/stlib/prefabs/visual.py similarity index 100% rename from stlib/core/visual_model.py rename to stlib/prefabs/visual.py diff --git a/stlib/shapes/__init__.py b/stlib/shapes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/shapes/cube.py b/stlib/shapes/cube.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/shapes/sphere.py b/stlib/shapes/sphere.py new file mode 100644 index 000000000..e69de29bb From 4380356e636425a4f1fe7047f4d7cd53798a61f4 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 15:18:53 +0200 Subject: [PATCH 14/41] Add gemetries --- examples/stlib/PrefabScene_beginner.py | 12 ++------ stlib/core/{base_entity.py => baseEntity.py} | 0 .../parameters.py => core/baseGeometry.py} | 0 stlib/core/baseParameters.py | 13 +++++++++ stlib/core/basePrefab.py | 11 +++++++ stlib/core/base_parameters.py | 26 ----------------- stlib/core/base_prefab.py | 10 ------- stlib/entities/deformable/__deformable__.py | 20 +++++-------- stlib/entities/deformable/__parameters__.py | 12 ++++++++ stlib/geometry/__geometry__.py | 29 +++++++++++++++++++ stlib/{shapes => geometry}/__init__.py | 0 stlib/{shapes => geometry}/cube.py | 0 stlib/{shapes => geometry}/sphere.py | 0 13 files changed, 75 insertions(+), 58 deletions(-) rename stlib/core/{base_entity.py => baseEntity.py} (100%) rename stlib/{entities/deformable/parameters.py => core/baseGeometry.py} (100%) create mode 100644 stlib/core/baseParameters.py create mode 100644 stlib/core/basePrefab.py delete mode 100644 stlib/core/base_parameters.py delete mode 100644 stlib/core/base_prefab.py create mode 100644 stlib/entities/deformable/__parameters__.py create mode 100644 stlib/geometry/__geometry__.py rename stlib/{shapes => geometry}/__init__.py (100%) rename stlib/{shapes => geometry}/cube.py (100%) rename stlib/{shapes => geometry}/sphere.py (100%) diff --git a/examples/stlib/PrefabScene_beginner.py b/examples/stlib/PrefabScene_beginner.py index a507bd529..2c95cc013 100644 --- a/examples/stlib/PrefabScene_beginner.py +++ b/examples/stlib/PrefabScene_beginner.py @@ -1,16 +1,8 @@ -from splib.helper import exportScene -from stlib.simulated.deformable import Deformable - +from stlib.entities.deformable import Deformable def createScene(root): - params = Deformable.Parameters() + params = Deformable.getParameters() params.name = "Logo" params.topology.filename="share/mesh/SOFA-logo.obj" root.add(Deformable, params=params) return root - - -if __name__=="__main__": - root = exportScene() - createScene(root) - pass \ No newline at end of file diff --git a/stlib/core/base_entity.py b/stlib/core/baseEntity.py similarity index 100% rename from stlib/core/base_entity.py rename to stlib/core/baseEntity.py diff --git a/stlib/entities/deformable/parameters.py b/stlib/core/baseGeometry.py similarity index 100% rename from stlib/entities/deformable/parameters.py rename to stlib/core/baseGeometry.py diff --git a/stlib/core/baseParameters.py b/stlib/core/baseParameters.py new file mode 100644 index 000000000..5d9a66a02 --- /dev/null +++ b/stlib/core/baseParameters.py @@ -0,0 +1,13 @@ +import dataclasses +from splib.core.utils import DEFAULT_VALUE + +import dataclasses +from types import Callable, Optional + +@dataclasses.dataclass +class BaseParameters(object): + name : str = "Entity" + kwargs : dict = dataclasses.field(default_factory=dict) + + def toDict(self): + return dataclasses.asdict(self) diff --git a/stlib/core/basePrefab.py b/stlib/core/basePrefab.py new file mode 100644 index 000000000..22652f744 --- /dev/null +++ b/stlib/core/basePrefab.py @@ -0,0 +1,11 @@ +import Sofa.Core +from baseParameters import BaseParameters + + +class BasePrefab(Sofa.Core.Prefab): + """ + A Prefab is a Sofa.Node that assembles a set of components and nodes + """ + + def __init__(self, params: BaseParameters): + Sofa.Core.Prefab.__init__(self, name=params.name) \ No newline at end of file diff --git a/stlib/core/base_parameters.py b/stlib/core/base_parameters.py deleted file mode 100644 index d1257b4bb..000000000 --- a/stlib/core/base_parameters.py +++ /dev/null @@ -1,26 +0,0 @@ -import dataclasses -from splib.core.utils import DEFAULT_VALUE - -from stlib.shapes import Shape - - -@dataclasses.dataclass -class BaseParameters(object): - name : str = "Entity" - shape : Shape = Shape() - kwargs : dict = dataclasses.field(default_factory=dict) - - def __getattr__(self, name: str) : - if name == "__getstate__": - getattr(BaseParameters, "__getstate__") - if name == "__setstate__": - getattr(BaseParameters, "__setstate__") - - try: - a = self.__getattribute__(name) - except Exception as e: - return DEFAULT_VALUE - return a - - def to_dict(self): - return dataclasses.asdict(self) diff --git a/stlib/core/base_prefab.py b/stlib/core/base_prefab.py deleted file mode 100644 index 067bc5bae..000000000 --- a/stlib/core/base_prefab.py +++ /dev/null @@ -1,10 +0,0 @@ -import Sofa.Core - - -class BasePrefab(Sofa.Core.Prefab): - """ - A Prefab is a Sofa.Node that assembles a set of components and nodes - """ - - def __init__(self): - Sofa.Core.Prefab.__init__(self) \ No newline at end of file diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 933c8806a..1e91f84d0 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -1,18 +1,14 @@ -from stlib.core.entity import Entity -from stlib.core.parameters import EntityParameters +from stlib.entities import Entity +from stlib.entities.deformable.__parameters__ import DeformableParameters -import dataclasses class Deformable(Entity): - @dataclasses.dataclass - class Parameters(EntityParameters): - addConstitutiveLaw : Callable = lambda x: x + + @staticmethod + def getParameters() -> DeformableParameters: + return DeformableParameters() + - mass : Optional[float] = None - - def to_dict(self): - return dataclasses.asdict(self) - - def __init__(self, **kwargs): + def __init__(self, params : DeformableParameters, **kwargs): Entity.__init__(self, **kwargs) diff --git a/stlib/entities/deformable/__parameters__.py b/stlib/entities/deformable/__parameters__.py new file mode 100644 index 000000000..e7126b159 --- /dev/null +++ b/stlib/entities/deformable/__parameters__.py @@ -0,0 +1,12 @@ + +import dataclasses +from stlib.core.base_parameters import BaseParameters + +@dataclasses.dataclass +class DeformableParameters(BaseParameters): + addConstitutiveLaw : Callable = lambda x: x + + mass : Optional[float] = None + + def to_dict(self): + return dataclasses.asdict(self) diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py new file mode 100644 index 000000000..f4af08050 --- /dev/null +++ b/stlib/geometry/__geometry__.py @@ -0,0 +1,29 @@ +from stlib.core.basePrefab import BasePrefab +from stlib.core.baseParameters import BaseParameters, Optional +from splib.topology.loader import loadMesh +from splib.core.enum_types import ElementType +from Sofa.Core import Object + +class Geometry(BasePrefab): + topology : Object # This should be more specialized into the right SOFA type + topologyModifier : Optional[Object] + + def __init__(self, *args, **kwargs): + BasePrefab.__init__(self, *args, **kwargs) + + +class GeometryParameter(BaseParameters): + isDynamic : bool + elementType : Optional[ElementType] + + +class FileGeometryParamtetrs(GeometryParameter): + filename : str + + +class FileGeometry(Geometry): + fileLoader : Object + + def __init__(self): + pass + \ No newline at end of file diff --git a/stlib/shapes/__init__.py b/stlib/geometry/__init__.py similarity index 100% rename from stlib/shapes/__init__.py rename to stlib/geometry/__init__.py diff --git a/stlib/shapes/cube.py b/stlib/geometry/cube.py similarity index 100% rename from stlib/shapes/cube.py rename to stlib/geometry/cube.py diff --git a/stlib/shapes/sphere.py b/stlib/geometry/sphere.py similarity index 100% rename from stlib/shapes/sphere.py rename to stlib/geometry/sphere.py From ba978cea1f4420311daed5798036ed7e5c821ba4 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 15:41:39 +0200 Subject: [PATCH 15/41] WIP FileParameters --- stlib/entities/__entity__.py | 35 +++++++++-------- stlib/geometry/__geometry__.py | 71 ++++++++++++++++++++++++++++------ 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index 8d2dd6f55..78a2487b1 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -1,8 +1,25 @@ -from stlib.core.parameters import EntityParameters +from stlib.core.baseParameters import BaseParameters import dataclasses from typing import Callable, Optional, overload import Sofa +@dataclasses.dataclass +class EntityParameters(BaseParameters): + name : str = "Entity" + + addSimulation : Callable = Simulation + addCollisionModel : Callable = CollisionModel + addVisualModel : Callable = VisualModel + + #setConstitutiveLaw # : Callable = addBidule + #setBoundaryCondition #: Callable = addBidule + + mechanical : dict = dataclasses.field(default_factory=dict) + collision : CollisionModel.Parameters = CollisionModel.Parameters() + visual : VisualModelParameters = VisualModelParameters() + simulation : SimulationParameters = SimulationParameters() + + class Entity(Sofa.Core.BaseEntity): @@ -13,22 +30,6 @@ class Entity(Sofa.Core.BaseEntity): parameters : EntityParameters - @dataclasses.dataclass - class Parameters(EntityParameters): - name : str = "Entity" - - addSimulation : Callable = Simulation - addCollisionModel : Callable = CollisionModel - addVisualModel : Callable = VisualModel - - #setConstitutiveLaw # : Callable = addBidule - #setBoundaryCondition #: Callable = addBidule - - mechanical : dict = dataclasses.field(default_factory=dict) - collision : CollisionModel.Parameters = CollisionModel.Parameters() - visual : VisualModelParameters = VisualModelParameters() - simulation : SimulationParameters = SimulationParameters() - def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): Sofa.Core.Node.__init__(self, name=parameters.name) diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py index f4af08050..b0d8a6f3a 100644 --- a/stlib/geometry/__geometry__.py +++ b/stlib/geometry/__geometry__.py @@ -1,29 +1,76 @@ from stlib.core.basePrefab import BasePrefab -from stlib.core.baseParameters import BaseParameters, Optional +from stlib.core.baseParameters import BaseParameters, Optional, dataclasses from splib.topology.loader import loadMesh +from splib.topology.dynamic import addDynamicTopology from splib.core.enum_types import ElementType from Sofa.Core import Object -class Geometry(BasePrefab): - topology : Object # This should be more specialized into the right SOFA type - topologyModifier : Optional[Object] - - def __init__(self, *args, **kwargs): - BasePrefab.__init__(self, *args, **kwargs) +@dataclasses.dataclass class GeometryParameter(BaseParameters): - isDynamic : bool + isDynamic : bool = False elementType : Optional[ElementType] + positions : Optional[list] + edges : Optional[list] + triangles : Optional[list] + tetrahedra : Optional[list] + hexahedra : Optional[list] + quads : Optional[list] + + +class Geometry(BasePrefab): + container : Object # This should be more specialized into the right SOFA type + modifier : Optional[Object] + def __init__(self, params: GeometryParameter): + BasePrefab.__init__(self, params) + if(param.isDynamic): + if(param.elementType is not None): + addDynamicTopology(self, param.elementType, **kargs) + else: + raise ValueError -class FileGeometryParamtetrs(GeometryParameter): + +@dataclasses.dataclass +class FileGeometryParameters(GeometryParameter): filename : str class FileGeometry(Geometry): - fileLoader : Object + loader : Object + + def __init__(self,param : FileGeometryParameters): + loadMesh(self, param.filename, **param.kwargs) + param.positions = "@loader.positions" + param.edges = "@loader.edges" + param.positions = "@loader.positions" + param.positions = "@loader.positions" + Geometry.__init__(self,param) - def __init__(self): pass - \ No newline at end of file + + + +def createScene(root): + from stlib.entities.deformable import Deformable + + fileParameters = FileGeometryParameters(filename = "MyFile.obj", + isDynamic = True, + elementType=ElementType.TETRA, + name="geometry") + + + + cubeGeometry = FileGeometry(FileGeometryParameters) + + entityParam = Deformable.getParameters( geometry = cubeGeometry, name="MyCube") + + + myCube = root.addChild("MyCube") + cubeGeomtry = myCube.addChild("geometry") + + cubeGeomtry.addObject("MeshObjLoader", name="loader", filenmae="MyFile.obj") + cubeGeomtry.addObject("TetrahedronSetTopologyContainer", name="container", positions="@loader.positions") + cubeGeomtry.addObject("TetrahedronSetTopologyModifier", name="modifier") + From fc881002fbba1cc42206b54312b5c195676dd6ac Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 17:49:47 +0200 Subject: [PATCH 16/41] Add beginner example --- examples/stlib/PrefabScene_beginner.py | 32 +++++++-- splib/topology/loader.py | 6 +- stlib/__init__.py | 2 +- stlib/core/baseEntity.py | 2 +- stlib/core/baseParameters.py | 2 +- stlib/entities/__entity__.py | 23 +++--- stlib/entities/__init__.py | 1 + stlib/entities/deformable/__deformable__.py | 4 +- stlib/entities/deformable/__init__.py | 2 + stlib/entities/deformable/__parameters__.py | 6 +- stlib/entities/rigid/__init__.py | 1 + stlib/entities/rigid/__parameters__.py | 10 +++ stlib/entities/rigid/__rigid__.py | 10 ++- stlib/entities/rigid/parameters.py | 0 stlib/geometry/__geometry__.py | 79 ++++++--------------- stlib/geometry/__init__.py | 1 + stlib/geometry/cube.py | 14 ++++ stlib/geometry/file.py | 24 +++++++ stlib/geometry/sphere.py | 14 ++++ 19 files changed, 146 insertions(+), 87 deletions(-) create mode 100644 stlib/entities/rigid/__parameters__.py delete mode 100644 stlib/entities/rigid/parameters.py create mode 100644 stlib/geometry/file.py diff --git a/examples/stlib/PrefabScene_beginner.py b/examples/stlib/PrefabScene_beginner.py index 2c95cc013..9d7ec24ac 100644 --- a/examples/stlib/PrefabScene_beginner.py +++ b/examples/stlib/PrefabScene_beginner.py @@ -1,8 +1,32 @@ +from stlib.entities.rigid import Rigid from stlib.entities.deformable import Deformable +from stlib.geometry.cube import CubeParameters +from stlib.geometry.file import FileParameters +from splib.simulation.headers import setupLagrangianCollision +from splib.simulation.linear_solvers import addLinearSolver +from splib.simulation.ode_solvers import addImplicitODE + +#To be added in splib +def addSolvers(root): + addLinearSolver(root) + addImplicitODE(root) + root.addObject("LinearSolverConstraintCorrection", linearsolver="@LinearSolver") def createScene(root): - params = Deformable.getParameters() - params.name = "Logo" - params.topology.filename="share/mesh/SOFA-logo.obj" - root.add(Deformable, params=params) + root.gravity = [0, 0, -9.81] + + setupLagrangianCollision(root) + addSolvers(root) + + rigidParams = Rigid.getParameters() + rigidParams.geometry = CubeParameters([0, 0, 0], 1, 3) + root.add(Rigid,rigidParams) + + + deformableParams = Deformable.getParameters() + #Add transformation somewhere here + deformableParams.geometry = FileParameters("SofaScene/Logo.vtk") + root.add(Deformable,deformableParams) + + return root diff --git a/splib/topology/loader.py b/splib/topology/loader.py index f9dd55fbb..b040491aa 100644 --- a/splib/topology/loader.py +++ b/splib/topology/loader.py @@ -9,11 +9,11 @@ def loadMesh(node,filename,**kwargs): if splitedName[-1] in ['vtk', 'obj', 'stl', 'msh', 'sph']: if splitedName[-1] == "msh": - return node.addObject("MeshGmshLoader", name="meshLoader",filename=filename, **kwargs) + return node.addObject("MeshGmshLoader", name="loader",filename=filename, **kwargs) elif splitedName[-1] == "sph": - return node.addObject("SphereLoader", name="meshLoader",filename=filename, **kwargs) + return node.addObject("SphereLoader", name="loader",filename=filename, **kwargs) else: - return node.addObject("Mesh"+splitedName[-1].upper()+"Loader", name="meshLoader",filename=filename, **kwargs) + return node.addObject("Mesh"+splitedName[-1].upper()+"Loader", name="loader",filename=filename, **kwargs) else: print('[Error] : File extension ' + splitedName[-1] + ' not recognised.') diff --git a/stlib/__init__.py b/stlib/__init__.py index d3aa4e81c..70108a419 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -1 +1 @@ -__all__ = ["core","simulated"] +__all__ = ["core","entities","prefabs","shapes"] diff --git a/stlib/core/baseEntity.py b/stlib/core/baseEntity.py index a9be8df3a..f93fc050e 100644 --- a/stlib/core/baseEntity.py +++ b/stlib/core/baseEntity.py @@ -1,5 +1,5 @@ import Sofa.Core -from base_parameters import BaseParameters +from baseParameters import BaseParameters class BaseEntity(Sofa.Core.Prefab): diff --git a/stlib/core/baseParameters.py b/stlib/core/baseParameters.py index 5d9a66a02..8d8ada38f 100644 --- a/stlib/core/baseParameters.py +++ b/stlib/core/baseParameters.py @@ -6,7 +6,7 @@ @dataclasses.dataclass class BaseParameters(object): - name : str = "Entity" + name : str = "object" kwargs : dict = dataclasses.field(default_factory=dict) def toDict(self): diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index 78a2487b1..181fb941f 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -1,23 +1,26 @@ from stlib.core.baseParameters import BaseParameters import dataclasses -from typing import Callable, Optional, overload +from typing import Callable, Optional, overload, Any +from stlib.geometry import GeometryParameters import Sofa @dataclasses.dataclass class EntityParameters(BaseParameters): - name : str = "Entity" + name = "Entity" + + # addSimulation : Callable = Simulation + # addCollisionModel : Callable = CollisionModel + # addVisualModel : Callable = VisualModel + - addSimulation : Callable = Simulation - addCollisionModel : Callable = CollisionModel - addVisualModel : Callable = VisualModel #setConstitutiveLaw # : Callable = addBidule #setBoundaryCondition #: Callable = addBidule - - mechanical : dict = dataclasses.field(default_factory=dict) - collision : CollisionModel.Parameters = CollisionModel.Parameters() - visual : VisualModelParameters = VisualModelParameters() - simulation : SimulationParameters = SimulationParameters() + geometry : GeometryParameters + # mechanical : dict = dataclasses.field(default_factory=dict) + # collision : CollisionModel.Parameters = CollisionModel.Parameters() + # visual : VisualModelParameters = VisualModelParameters() + # simulation : SimulationParameters = SimulationParameters() diff --git a/stlib/entities/__init__.py b/stlib/entities/__init__.py index e69de29bb..a207a568f 100644 --- a/stlib/entities/__init__.py +++ b/stlib/entities/__init__.py @@ -0,0 +1 @@ +from __entity__ import * \ No newline at end of file diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 1e91f84d0..db629c9a0 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -5,8 +5,8 @@ class Deformable(Entity): @staticmethod - def getParameters() -> DeformableParameters: - return DeformableParameters() + def getParameters(**kwargs) -> DeformableParameters: + return DeformableParameters(**kwargs) def __init__(self, params : DeformableParameters, **kwargs): diff --git a/stlib/entities/deformable/__init__.py b/stlib/entities/deformable/__init__.py index e69de29bb..e23887d67 100644 --- a/stlib/entities/deformable/__init__.py +++ b/stlib/entities/deformable/__init__.py @@ -0,0 +1,2 @@ +from __deformable__ import * +from __parameters__ import * \ No newline at end of file diff --git a/stlib/entities/deformable/__parameters__.py b/stlib/entities/deformable/__parameters__.py index e7126b159..e41ec3707 100644 --- a/stlib/entities/deformable/__parameters__.py +++ b/stlib/entities/deformable/__parameters__.py @@ -1,6 +1,4 @@ - -import dataclasses -from stlib.core.base_parameters import BaseParameters +from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses @dataclasses.dataclass class DeformableParameters(BaseParameters): @@ -8,5 +6,5 @@ class DeformableParameters(BaseParameters): mass : Optional[float] = None - def to_dict(self): + def toDict(self): return dataclasses.asdict(self) diff --git a/stlib/entities/rigid/__init__.py b/stlib/entities/rigid/__init__.py index e69de29bb..17b4b3b61 100644 --- a/stlib/entities/rigid/__init__.py +++ b/stlib/entities/rigid/__init__.py @@ -0,0 +1 @@ +from __rigid__ import * \ No newline at end of file diff --git a/stlib/entities/rigid/__parameters__.py b/stlib/entities/rigid/__parameters__.py new file mode 100644 index 000000000..868ec86b4 --- /dev/null +++ b/stlib/entities/rigid/__parameters__.py @@ -0,0 +1,10 @@ +from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses + +@dataclasses.dataclass +class DRigidParameters(BaseParameters): + addConstitutiveLaw : Callable = lambda x: x + + mass : Optional[float] = None + + def toDict(self): + return dataclasses.asdict(self) diff --git a/stlib/entities/rigid/__rigid__.py b/stlib/entities/rigid/__rigid__.py index f830eab39..69c68f6d0 100644 --- a/stlib/entities/rigid/__rigid__.py +++ b/stlib/entities/rigid/__rigid__.py @@ -1,9 +1,15 @@ -from stlib.core.entity import Entity -from stlib.core.parameters import EntityParameters +from stlib.entities import Entity +from stlib.entities.rigid.__parameters__ import RigidParameters import dataclasses class Rigid(Entity): + + @staticmethod + def getParameters(**kwargs) -> RigidParameters: + return RigidParameters(**kwargs) + + def __init__(self, **kwargs): Entity.__init__(self, **kwargs) diff --git a/stlib/entities/rigid/parameters.py b/stlib/entities/rigid/parameters.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py index b0d8a6f3a..1927ddf27 100644 --- a/stlib/geometry/__geometry__.py +++ b/stlib/geometry/__geometry__.py @@ -1,76 +1,37 @@ from stlib.core.basePrefab import BasePrefab -from stlib.core.baseParameters import BaseParameters, Optional, dataclasses -from splib.topology.loader import loadMesh +from stlib.core.baseParameters import BaseParameters, Optional, dataclasses, Any from splib.topology.dynamic import addDynamicTopology +from splib.topology.static import addStaticTopology from splib.core.enum_types import ElementType from Sofa.Core import Object - - @dataclasses.dataclass -class GeometryParameter(BaseParameters): - isDynamic : bool = False - elementType : Optional[ElementType] - positions : Optional[list] - edges : Optional[list] - triangles : Optional[list] - tetrahedra : Optional[list] - hexahedra : Optional[list] - quads : Optional[list] - +class GeometryParameters(BaseParameters): + @dataclasses.dataclass + class Data(object): + elementType : Optional[ElementType] + positions : Any + edges : Optional[Any] + triangles : Optional[Any] + tetrahedra : Optional[Any] + hexahedra : Optional[Any] + quads : Optional[Any] + dynamicTopology : bool = False + data : Data class Geometry(BasePrefab): container : Object # This should be more specialized into the right SOFA type modifier : Optional[Object] - def __init__(self, params: GeometryParameter): + def __init__(self, params: GeometryParameters): BasePrefab.__init__(self, params) - if(param.isDynamic): - if(param.elementType is not None): - addDynamicTopology(self, param.elementType, **kargs) + if(params.dynamicTopology): + if(params.elementType is not None): + addDynamicTopology(self, **dataclasses.asdict(params.data), **params.kargs) else: raise ValueError + else: + addStaticTopology(self, **dataclasses.asdict(params.data), **params.kargs) -@dataclasses.dataclass -class FileGeometryParameters(GeometryParameter): - filename : str - - -class FileGeometry(Geometry): - loader : Object - - def __init__(self,param : FileGeometryParameters): - loadMesh(self, param.filename, **param.kwargs) - param.positions = "@loader.positions" - param.edges = "@loader.edges" - param.positions = "@loader.positions" - param.positions = "@loader.positions" - Geometry.__init__(self,param) - - pass - - - -def createScene(root): - from stlib.entities.deformable import Deformable - - fileParameters = FileGeometryParameters(filename = "MyFile.obj", - isDynamic = True, - elementType=ElementType.TETRA, - name="geometry") - - - - cubeGeometry = FileGeometry(FileGeometryParameters) - - entityParam = Deformable.getParameters( geometry = cubeGeometry, name="MyCube") - - - myCube = root.addChild("MyCube") - cubeGeomtry = myCube.addChild("geometry") - cubeGeomtry.addObject("MeshObjLoader", name="loader", filenmae="MyFile.obj") - cubeGeomtry.addObject("TetrahedronSetTopologyContainer", name="container", positions="@loader.positions") - cubeGeomtry.addObject("TetrahedronSetTopologyModifier", name="modifier") - diff --git a/stlib/geometry/__init__.py b/stlib/geometry/__init__.py index e69de29bb..e954fbdcb 100644 --- a/stlib/geometry/__init__.py +++ b/stlib/geometry/__init__.py @@ -0,0 +1 @@ +from __geometry__ import * \ No newline at end of file diff --git a/stlib/geometry/cube.py b/stlib/geometry/cube.py index e69de29bb..0c8ac7615 100644 --- a/stlib/geometry/cube.py +++ b/stlib/geometry/cube.py @@ -0,0 +1,14 @@ +from stlib.geometry import GeometryParameters + +class CubeParameters(GeometryParameters): + def __init__(self, center, edgeLength, pointPerEdge, dynamicTopology = False): + + customGeom = CubeParameters.createData(center, edgeLength, pointPerEdge) + GeometryParameters.__init__(data = customGeom, dynamicTopology = dynamicTopology) + + @staticmethod + def createData(center, edgeLength, pointPerEdge) -> GeometryParameters.Data : + data = GeometryParameters.Data() + #Fill data + return data + \ No newline at end of file diff --git a/stlib/geometry/file.py b/stlib/geometry/file.py new file mode 100644 index 000000000..7504c321e --- /dev/null +++ b/stlib/geometry/file.py @@ -0,0 +1,24 @@ +from stlib.geometry import GeometryParameters +from splib.topology.loader import loadMesh +from Sofa.Core import Node + +class FileParameters(GeometryParameters): + def __init__(self, filename, dynamicTopology = False): + + customGeom = FileParameters.createData(filename) + GeometryParameters.__init__(data = customGeom, dynamicTopology = dynamicTopology) + + @staticmethod + def createData(filename) -> GeometryParameters.Data : + data = GeometryParameters.Data() + node = Sofa.Core.Node() + loadMesh(node, filename) + node.init() + + data.positions = node.loader.position.value + data.edges = node.loader.edges.value + data.triangles = node.loader.triangles.value + data.tetrahedra = node.loader.tetras.value + + return data + \ No newline at end of file diff --git a/stlib/geometry/sphere.py b/stlib/geometry/sphere.py index e69de29bb..2c3a58ce6 100644 --- a/stlib/geometry/sphere.py +++ b/stlib/geometry/sphere.py @@ -0,0 +1,14 @@ +from stlib.geometry import GeometryParameters + +class SphereParameters(GeometryParameters): + def __init__(self, center, radius, pointPerRad, dynamicTopology = False): + + customGeom = SphereParameters.createData(center, radius, pointPerRad) + GeometryParameters.__init__(data = customGeom, dynamicTopology = dynamicTopology) + + @staticmethod + def createData(center, radius, pointPerRad) -> GeometryParameters.Data : + data = GeometryParameters.Data() + #Fill data + return data + \ No newline at end of file From 7c314c6a5ff58b9b571bef5dbf5c77807d1f4a9c Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 17:50:24 +0200 Subject: [PATCH 17/41] Add unsaved changes --- stlib/entities/rigid/__parameters__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stlib/entities/rigid/__parameters__.py b/stlib/entities/rigid/__parameters__.py index 868ec86b4..ce8e0a04f 100644 --- a/stlib/entities/rigid/__parameters__.py +++ b/stlib/entities/rigid/__parameters__.py @@ -1,8 +1,7 @@ from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses @dataclasses.dataclass -class DRigidParameters(BaseParameters): - addConstitutiveLaw : Callable = lambda x: x +class RigidParameters(BaseParameters): mass : Optional[float] = None From b70a52bc2247dff239cc4a2c5e93143b4efbba55 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Mon, 14 Apr 2025 18:22:59 +0200 Subject: [PATCH 18/41] Start work on visual and collision --- stlib/entities/deformable/__deformable__.py | 26 +++++++++++-- stlib/entities/deformable/__parameters__.py | 12 +++++- stlib/entities/rigid/__parameters__.py | 2 + stlib/geometry/file.py | 1 + stlib/prefabs/collision.py | 18 +++++++++ stlib/prefabs/visual.py | 41 ++++++++------------- 6 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 stlib/prefabs/collision.py diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index db629c9a0..3d8f94f20 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -1,14 +1,34 @@ from stlib.entities import Entity from stlib.entities.deformable.__parameters__ import DeformableParameters - +from stlib.geometry import Geometry +from stlib.prefabs.visual import Visual +from stlib.prefabs.collision import Collision class Deformable(Entity): - + params : DeformableParameters + @staticmethod def getParameters(**kwargs) -> DeformableParameters: return DeformableParameters(**kwargs) def __init__(self, params : DeformableParameters, **kwargs): - Entity.__init__(self, **kwargs) + Entity.__init__(self, **kwargs) + self.add(Geometry, params.geometry) + self.__addConstitutiveLaw__() + self.add(Collision, params.collision) + + #@customizable + # Need generic way of defining paramaters (linear/hyper...) + def __addConstitutiveLaw__(self): + self.params.addConstitutiveLaw() + + + + #@customizable + def __addVisual__(self): + #Exctract surface and add indentity mapping + + self.add(Visual, params.visual) + pass diff --git a/stlib/entities/deformable/__parameters__.py b/stlib/entities/deformable/__parameters__.py index e41ec3707..d64c29f0e 100644 --- a/stlib/entities/deformable/__parameters__.py +++ b/stlib/entities/deformable/__parameters__.py @@ -1,10 +1,18 @@ from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses +from stlib.geometry import GeometryParameters +from stlib.prefabs.visual import VisualParameters +from stlib.prefabs.collision import CollisionParameters @dataclasses.dataclass class DeformableParameters(BaseParameters): - addConstitutiveLaw : Callable = lambda x: x - mass : Optional[float] = None + geometry : GeometryParameters + # Add default value + visual : VisualParameters + collision : CollisionParameters + mass : float = float + + addConstitutiveLaw : Callable = lambda x: x def toDict(self): return dataclasses.asdict(self) diff --git a/stlib/entities/rigid/__parameters__.py b/stlib/entities/rigid/__parameters__.py index ce8e0a04f..f7be03c77 100644 --- a/stlib/entities/rigid/__parameters__.py +++ b/stlib/entities/rigid/__parameters__.py @@ -1,8 +1,10 @@ from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses +from stlib.geometry import GeometryParameters @dataclasses.dataclass class RigidParameters(BaseParameters): + geometry : GeometryParameters mass : Optional[float] = None def toDict(self): diff --git a/stlib/geometry/file.py b/stlib/geometry/file.py index 7504c321e..18e40ebb8 100644 --- a/stlib/geometry/file.py +++ b/stlib/geometry/file.py @@ -11,6 +11,7 @@ def __init__(self, filename, dynamicTopology = False): @staticmethod def createData(filename) -> GeometryParameters.Data : data = GeometryParameters.Data() + node = Sofa.Core.Node() loadMesh(node, filename) node.init() diff --git a/stlib/prefabs/collision.py b/stlib/prefabs/collision.py new file mode 100644 index 000000000..bb73fe4bc --- /dev/null +++ b/stlib/prefabs/collision.py @@ -0,0 +1,18 @@ +from stlib.core.basePrefab import BasePrefab +from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any +from stlib.geometry import GeometryParameters +from Sofa.Core import Object + +@dataclasses.dataclass +class CollisionParameters(BaseParameters): + primitives : list[Any] #TODO: add enum type in splib + + geometry : GeometryParameters + addMapping : Callable + + +class Collision(BasePrefab): + + def __init__(self, params: GeometryParameters): + BasePrefab.__init__(self, params) + diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index 7081c9b2c..2a9590b84 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -1,32 +1,21 @@ -from stlib.core.parameters import EntityParameters -import dataclasses -import Sofa +from stlib.core.basePrefab import BasePrefab +from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any +from stlib.geometry import GeometryParameters +from Sofa.Core import Object -class VisualModel(Sofa.Core.BasePrefab): - @dataclasses.dataclass - class Parameters(EntityParameters): - name : str = "VisualModel" +@dataclasses.dataclass +class VisualParameters(BaseParameters): + color : Optional[list[float]] + texture : Optional[str] - filename : str = "mesh/sphere_02.obj" - - renderer : dict = dataclasses.field(default_factory=dict) - mapping : dict = dataclasses.field(default_factory=dict) + geometry : GeometryParameters + addMapping : Optional[Callable] +class Visual(BasePrefab): + container : Object # This should be more specialized into the right SOFA type + modifier : Optional[Object] - def __init__(self, parent=None, parameters : VisualModelParameters = VisualModelParameters()): - Sofa.Core.Node.__init__(self, name=parameters.name) + def __init__(self, params: GeometryParameters): + BasePrefab.__init__(self, params) - if parent != None: - parent.addChild(self) - - self.addObject("MeshOBJLoader", name="loader", filename=parameters.filename) - self.addRenderer(**to_dict(parameters.renderer) | {"src" : "@loader"} ) - self.addMapping(**to_dict(parameters.mapping) ) - - def addRenderer(self, **kwargs): - self.addObject("OglModel", name="renderer", **kwargs) - - def addMapping(self, **kwargs): - self.addObject("RigidMapping", name="mapping", **kwargs) - \ No newline at end of file From 7cedc423e3f29de73dea38e8e760d7627fffe1d5 Mon Sep 17 00:00:00 2001 From: hugtalbot Date: Tue, 15 Apr 2025 13:53:42 +0200 Subject: [PATCH 19/41] work on Tuesday : visual, geometry --- stlib/core/basePrefab.py | 32 +++++++++++++++++++-- stlib/entities/__entity__.py | 2 ++ stlib/entities/deformable/__deformable__.py | 6 ++-- stlib/geometry/__geometry__.py | 13 +++++---- stlib/geometry/file.py | 2 ++ stlib/prefabs/visual.py | 11 +++++-- 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/stlib/core/basePrefab.py b/stlib/core/basePrefab.py index 22652f744..cd391c582 100644 --- a/stlib/core/basePrefab.py +++ b/stlib/core/basePrefab.py @@ -1,5 +1,29 @@ +import copy import Sofa.Core -from baseParameters import BaseParameters +from stlib.core.basePrefabParameters import BasePrefabParameters + + +def addFromTypeName(self : Sofa.Core.Node, typeName, params = BasePrefabParameters, **kwargs): + def findName(cname, node): + """Compute a working unique name in the node""" + rname = cname + for i in range(0, len(node.children)): + if rname not in node.children: + return rname + rname = cname + str(i+1) + return rname + + for k,v in kwargs.items(): + if hasattr(params, k): + setattr(params, k, v) + + params = copy.copy(params) + if params.name in self.children: + params.name = findName(params.name, self) + + return typeName(parent = self, parameters=params) + +Sofa.Core.Node.add = addFromTypeName class BasePrefab(Sofa.Core.Prefab): @@ -8,4 +32,8 @@ class BasePrefab(Sofa.Core.Prefab): """ def __init__(self, params: BaseParameters): - Sofa.Core.Prefab.__init__(self, name=params.name) \ No newline at end of file + Sofa.Core.Prefab.__init__(self, name=params.name) + + def localToGlobalCoordinates(pointCloudInput, pointCloudOutput): + raise NotImplemented("Send an email to Damien, he will help you. Guaranteed :)") + diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index 181fb941f..705a154ab 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -43,6 +43,7 @@ def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): self.parameters = parameters self.addMechanicalModel(**parameters.mechanical) + # ???? self.addSimulation(parameters=parameters.simulation) self.addVisualModel(parameters=parameters.visual) self.addCollisionModel() @@ -50,6 +51,7 @@ def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): def addMechanicalModel(self, **kwargs): self.addObject("MechanicalObject", **kwargs) + # ???? def addSimulation(self, **kwargs): self.parameters.addSimulation(self, **kwargs) diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 3d8f94f20..3f024b230 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -18,17 +18,15 @@ def __init__(self, params : DeformableParameters, **kwargs): self.__addConstitutiveLaw__() self.add(Collision, params.collision) + #@customizable # Need generic way of defining paramaters (linear/hyper...) def __addConstitutiveLaw__(self): self.params.addConstitutiveLaw() - #@customizable def __addVisual__(self): - #Exctract surface and add indentity mapping - + #Extract surface and add identity mapping self.add(Visual, params.visual) - pass diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py index 1927ddf27..58cfcea82 100644 --- a/stlib/geometry/__geometry__.py +++ b/stlib/geometry/__geometry__.py @@ -9,13 +9,19 @@ class GeometryParameters(BaseParameters): @dataclasses.dataclass class Data(object): - elementType : Optional[ElementType] + # Initial positions positions : Any + + # Type of the highest degree element + elementType : Optional[ElementType] + + # Topology information edges : Optional[Any] triangles : Optional[Any] + quads : Optional[Any] tetrahedra : Optional[Any] hexahedra : Optional[Any] - quads : Optional[Any] + dynamicTopology : bool = False data : Data @@ -32,6 +38,3 @@ def __init__(self, params: GeometryParameters): raise ValueError else: addStaticTopology(self, **dataclasses.asdict(params.data), **params.kargs) - - - diff --git a/stlib/geometry/file.py b/stlib/geometry/file.py index 18e40ebb8..5247577ed 100644 --- a/stlib/geometry/file.py +++ b/stlib/geometry/file.py @@ -21,5 +21,7 @@ def createData(filename) -> GeometryParameters.Data : data.triangles = node.loader.triangles.value data.tetrahedra = node.loader.tetras.value + node.cleanUp() + return data \ No newline at end of file diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index 2a9590b84..4a41d2736 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -1,6 +1,6 @@ from stlib.core.basePrefab import BasePrefab from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any -from stlib.geometry import GeometryParameters +from stlib.geometry import Geometry, GeometryParameters from Sofa.Core import Object @dataclasses.dataclass @@ -16,6 +16,13 @@ class Visual(BasePrefab): container : Object # This should be more specialized into the right SOFA type modifier : Optional[Object] - def __init__(self, params: GeometryParameters): + def __init__(self, params: VisualParameters): BasePrefab.__init__(self, params) + geom = self.add(Geometry, params.geometry) + # TODO : handle optional color, texture using DEFAULT_VALUE mechanism (as implemented by Paul) + self.addObject("OglModel", color=params.color, src=geom.container.linkpath) + + if params.addMapping is not None: + params.addMapping(self) + From 5f183d5ab673ed9c36d2313b670865eecc6df2bc Mon Sep 17 00:00:00 2001 From: hugtalbot Date: Tue, 15 Apr 2025 14:17:18 +0200 Subject: [PATCH 20/41] add basePrefabParameters.py --- stlib/core/basePrefabParameters.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 stlib/core/basePrefabParameters.py diff --git a/stlib/core/basePrefabParameters.py b/stlib/core/basePrefabParameters.py new file mode 100644 index 000000000..9ea8911bd --- /dev/null +++ b/stlib/core/basePrefabParameters.py @@ -0,0 +1,15 @@ +import dataclasses + +@dataclasses.dataclass +class BasePrefabParameters(object): + name : str = "object" + kwargs : dict = dataclasses.field(default_factory=dict) + + # Transformation information + # TODO: these data are going to be added in Node in SOFA (C++ implementation) + translation : list[float] = [0., 0., 0.] + rotation : list[float] = [0., 0., 0.] + scale : list[float] = [1., 1., 1.] + + def toDict(self): + return dataclasses.asdict(self) From 97a23813f83926e88aab434fbd9cd80917d02a48 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Tue, 15 Apr 2025 15:03:42 +0200 Subject: [PATCH 21/41] Add extracted geometry --- stlib/geometry/__geometry__.py | 46 ++++++++++++++++++++++------------ stlib/geometry/extract.py | 46 ++++++++++++++++++++++++++++++++++ stlib/geometry/file.py | 42 +++++++++++++++++-------------- 3 files changed, 99 insertions(+), 35 deletions(-) create mode 100644 stlib/geometry/extract.py diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py index 58cfcea82..d2f7bd5fc 100644 --- a/stlib/geometry/__geometry__.py +++ b/stlib/geometry/__geometry__.py @@ -3,38 +3,52 @@ from splib.topology.dynamic import addDynamicTopology from splib.topology.static import addStaticTopology from splib.core.enum_types import ElementType +from splib.core.utils import DEFAULT_VALUE from Sofa.Core import Object + +class Geometry(BasePrefab):... + @dataclasses.dataclass -class GeometryParameters(BaseParameters): - @dataclasses.dataclass - class Data(object): - # Initial positions - positions : Any +class InternalDataProvider(object): + positions : Any + # Topology information + edges : Any = DEFAULT_VALUE + triangles : Any = DEFAULT_VALUE + quads : Any = DEFAULT_VALUE + tetrahedra : Any = DEFAULT_VALUE + hexahedra : Any = DEFAULT_VALUE + + def generateAttribute(self, parent : Geometry): + pass + - # Type of the highest degree element - elementType : Optional[ElementType] +@dataclasses.dataclass +class GeometryParameters(BaseParameters): - # Topology information - edges : Optional[Any] - triangles : Optional[Any] - quads : Optional[Any] - tetrahedra : Optional[Any] - hexahedra : Optional[Any] + # Type of the highest degree element + elementType : Optional[ElementType] dynamicTopology : bool = False - data : Data + data : Optional[InternalDataProvider] + + class Geometry(BasePrefab): container : Object # This should be more specialized into the right SOFA type modifier : Optional[Object] + params : GeometryParameters + def __init__(self, params: GeometryParameters): BasePrefab.__init__(self, params) + self.params = params + if params.data is not None : + params.data.generateAttribute(self) if(params.dynamicTopology): if(params.elementType is not None): - addDynamicTopology(self, **dataclasses.asdict(params.data), **params.kargs) + addDynamicTopology(self, container = {dataclasses.asdict(params.data)}) else: raise ValueError else: - addStaticTopology(self, **dataclasses.asdict(params.data), **params.kargs) + addStaticTopology(self, container = {dataclasses.asdict(params.data)}) diff --git a/stlib/geometry/extract.py b/stlib/geometry/extract.py new file mode 100644 index 000000000..ae9c97e69 --- /dev/null +++ b/stlib/geometry/extract.py @@ -0,0 +1,46 @@ +from stlib.geometry import GeometryParameters, InternalDataProvider, Geometry +from stlib.core.baseParameters import dataclasses +from splib.topology.dynamic import addDynamicTopology +from splib.topology.loader import loadMesh +from splib.core.enum_types import ElementType + +from Sofa.Core import Node + + +class ExtractInternalDataProvider(InternalDataProvider): + destElemType : ElementType + fromElemType : ElementType + fromNodeName : str + + def __post_init__(self): + if(not (self.fromElemType == ElementType.TETRA and self.destElemType == ElementType.TRIANGLES) + and not (self.fromElemType == ElementType.HEXA and self.destElemType == ElementType.QUAD) ): + raise ValueError("Only configuration possible are 'Tetra to Triangle' and 'Hexa to quad'") + + InternalDataProvider.__init__(self) + + + def generateAttribute(self, parent : Geometry): + tmn = parent.addChild("topologicalMappingNode") + + #TODO: Specify somewhere in the doc that this should only be used for mapped topologies that extract parent topology surface + fromLink = parent.parents[0].parents[0].getChild(self.fromNodeName).container.linkpath + addDynamicTopology(tmn, type=self.destElemType) + if self.fromElemType == ElementType.TETRA: + tmn.addObject("Tetra2TriangleTopologicalMapping", input=fromLink, output=tmn.container.linkpath) + elif self.fromElemType == ElementType.HEXA: + tmn.addObject("Hexa2QuadTopologicalMapping", input=fromLink, output=tmn.container.linkpath) + + self.positions = tmn.container.position.linkpath + self.edges = tmn.container.edges.linkpath + self.triangles = tmn.container.triangles.linkpath + self.quads = tmn.container.quads.linkpath + self.hexaedra = tmn.container.hexaedra.linkpath + self.tetrahedra = tmn.container.tetras.linkpath + + + +class ExtractParameters(GeometryParameters): + def __init__(self, fromGeometry : GeometryParameters, destElementType : ElementType, dynamicTopology = False, ): + GeometryParameters.__init__(data = ExtractInternalDataProvider(destElemType = destElementType, fromElemType = fromGeometry.elementType,fromNodeName = fromGeometry.name), dynamicTopology = dynamicTopology, elementType = destElementType) + diff --git a/stlib/geometry/file.py b/stlib/geometry/file.py index 5247577ed..1c15597df 100644 --- a/stlib/geometry/file.py +++ b/stlib/geometry/file.py @@ -1,27 +1,31 @@ -from stlib.geometry import GeometryParameters +from stlib.geometry import GeometryParameters, InternalDataProvider, Geometry +from stlib.core.baseParameters import dataclasses from splib.topology.loader import loadMesh +from splib.core.enum_types import ElementType + from Sofa.Core import Node -class FileParameters(GeometryParameters): - def __init__(self, filename, dynamicTopology = False): +@dataclasses.dataclass +class FileInternalDataProvider(InternalDataProvider): + filename : str - customGeom = FileParameters.createData(filename) - GeometryParameters.__init__(data = customGeom, dynamicTopology = dynamicTopology) + def __post_init__(self, **kwargs): + InternalDataProvider.__init__(self,**kwargs) - @staticmethod - def createData(filename) -> GeometryParameters.Data : - data = GeometryParameters.Data() - - node = Sofa.Core.Node() - loadMesh(node, filename) - node.init() + def generateAttribute(self, parent : Geometry): + + loadMesh(parent, self.filename) - data.positions = node.loader.position.value - data.edges = node.loader.edges.value - data.triangles = node.loader.triangles.value - data.tetrahedra = node.loader.tetras.value + self.positions = parent.loader.position.linkpath + self.edges = parent.loader.edges.linkpath + self.triangles = parent.loader.triangles.linkpath + self.quads = parent.loader.quads.linkpath + self.hexaedra = parent.loader.hexaedra.linkpath + self.tetrahedra = parent.loader.tetras.linkpath - node.cleanUp() - return data - \ No newline at end of file + +class FileParameters(GeometryParameters): + def __init__(self, filename, dynamicTopology = False, elementType : ElementType = None ): + GeometryParameters.__init__(data = FileInternalDataProvider(filename), dynamicTopology = dynamicTopology, elementType = elementType) + From 00fd16b4f22838570e7a18c755efac6315effd9e Mon Sep 17 00:00:00 2001 From: bakpaul Date: Tue, 15 Apr 2025 15:48:01 +0200 Subject: [PATCH 22/41] Fix visual example --- examples/stlib/PrefabScene_beginner.py | 2 -- examples/stlib/PrefabScene_intermediate.py | 8 +------- stlib/core/baseParameters.py | 2 +- stlib/core/basePrefab.py | 11 ++++++----- stlib/core/basePrefabParameters.py | 6 +++--- stlib/entities/deformable/__init__.py | 4 ++-- stlib/entities/rigid/__init__.py | 2 +- stlib/geometry/__geometry__.py | 12 +++++++----- stlib/geometry/__init__.py | 2 +- stlib/geometry/extract.py | 4 ++-- stlib/geometry/file.py | 16 ++++++++-------- stlib/prefabs/visual.py | 20 ++++++++++++++++---- 12 files changed, 48 insertions(+), 41 deletions(-) diff --git a/examples/stlib/PrefabScene_beginner.py b/examples/stlib/PrefabScene_beginner.py index 9d7ec24ac..ca59a63e5 100644 --- a/examples/stlib/PrefabScene_beginner.py +++ b/examples/stlib/PrefabScene_beginner.py @@ -22,11 +22,9 @@ def createScene(root): rigidParams.geometry = CubeParameters([0, 0, 0], 1, 3) root.add(Rigid,rigidParams) - deformableParams = Deformable.getParameters() #Add transformation somewhere here deformableParams.geometry = FileParameters("SofaScene/Logo.vtk") root.add(Deformable,deformableParams) - return root diff --git a/examples/stlib/PrefabScene_intermediate.py b/examples/stlib/PrefabScene_intermediate.py index 0bc427d37..7ce22c745 100644 --- a/examples/stlib/PrefabScene_intermediate.py +++ b/examples/stlib/PrefabScene_intermediate.py @@ -3,12 +3,6 @@ def createScene(root): - Entity.Deformable(Mesh,MechaProperties) - return root - -if __name__=="__main__": - root = exportScene() - createScene(root) - pass \ No newline at end of file + return root \ No newline at end of file diff --git a/stlib/core/baseParameters.py b/stlib/core/baseParameters.py index 8d8ada38f..883b17b5b 100644 --- a/stlib/core/baseParameters.py +++ b/stlib/core/baseParameters.py @@ -2,7 +2,7 @@ from splib.core.utils import DEFAULT_VALUE import dataclasses -from types import Callable, Optional +from typing import Callable, Optional, Any @dataclasses.dataclass class BaseParameters(object): diff --git a/stlib/core/basePrefab.py b/stlib/core/basePrefab.py index cd391c582..820e0e2b2 100644 --- a/stlib/core/basePrefab.py +++ b/stlib/core/basePrefab.py @@ -1,5 +1,6 @@ import copy -import Sofa.Core +import Sofa +import Sofa.Core from stlib.core.basePrefabParameters import BasePrefabParameters @@ -21,18 +22,18 @@ def findName(cname, node): if params.name in self.children: params.name = findName(params.name, self) - return typeName(parent = self, parameters=params) + return self.addChild(typeName(params)) Sofa.Core.Node.add = addFromTypeName -class BasePrefab(Sofa.Core.Prefab): +class BasePrefab(Sofa.Core.Node): """ A Prefab is a Sofa.Node that assembles a set of components and nodes """ - def __init__(self, params: BaseParameters): - Sofa.Core.Prefab.__init__(self, name=params.name) + def __init__(self, params: BasePrefabParameters): + Sofa.Core.Node.__init__(self, name=params.name) def localToGlobalCoordinates(pointCloudInput, pointCloudOutput): raise NotImplemented("Send an email to Damien, he will help you. Guaranteed :)") diff --git a/stlib/core/basePrefabParameters.py b/stlib/core/basePrefabParameters.py index 9ea8911bd..d6a69dee9 100644 --- a/stlib/core/basePrefabParameters.py +++ b/stlib/core/basePrefabParameters.py @@ -7,9 +7,9 @@ class BasePrefabParameters(object): # Transformation information # TODO: these data are going to be added in Node in SOFA (C++ implementation) - translation : list[float] = [0., 0., 0.] - rotation : list[float] = [0., 0., 0.] - scale : list[float] = [1., 1., 1.] + translation : list[float] = dataclasses.field(default_factory = lambda : [0., 0., 0.]) + rotation : list[float] = dataclasses.field(default_factory = lambda : [0., 0., 0.]) + scale : list[float] = dataclasses.field(default_factory = lambda : [1., 1., 1.]) def toDict(self): return dataclasses.asdict(self) diff --git a/stlib/entities/deformable/__init__.py b/stlib/entities/deformable/__init__.py index e23887d67..d17dc4db2 100644 --- a/stlib/entities/deformable/__init__.py +++ b/stlib/entities/deformable/__init__.py @@ -1,2 +1,2 @@ -from __deformable__ import * -from __parameters__ import * \ No newline at end of file +from .__deformable__ import * +from .__parameters__ import * \ No newline at end of file diff --git a/stlib/entities/rigid/__init__.py b/stlib/entities/rigid/__init__.py index 17b4b3b61..04efc302b 100644 --- a/stlib/entities/rigid/__init__.py +++ b/stlib/entities/rigid/__init__.py @@ -1 +1 @@ -from __rigid__ import * \ No newline at end of file +from .__rigid__ import * \ No newline at end of file diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py index d2f7bd5fc..32ed6b610 100644 --- a/stlib/geometry/__geometry__.py +++ b/stlib/geometry/__geometry__.py @@ -11,7 +11,7 @@ class Geometry(BasePrefab):... @dataclasses.dataclass class InternalDataProvider(object): - positions : Any + position : Any = None # Topology information edges : Any = DEFAULT_VALUE triangles : Any = DEFAULT_VALUE @@ -27,10 +27,12 @@ def generateAttribute(self, parent : Geometry): class GeometryParameters(BaseParameters): # Type of the highest degree element - elementType : Optional[ElementType] + elementType : Optional[ElementType] = None + data : Optional[InternalDataProvider] = None dynamicTopology : bool = False - data : Optional[InternalDataProvider] + + @@ -47,8 +49,8 @@ def __init__(self, params: GeometryParameters): params.data.generateAttribute(self) if(params.dynamicTopology): if(params.elementType is not None): - addDynamicTopology(self, container = {dataclasses.asdict(params.data)}) + addDynamicTopology(self, container = dataclasses.asdict(params.data)) else: raise ValueError else: - addStaticTopology(self, container = {dataclasses.asdict(params.data)}) + addStaticTopology(self, container = dataclasses.asdict(params.data)) diff --git a/stlib/geometry/__init__.py b/stlib/geometry/__init__.py index e954fbdcb..34a466c77 100644 --- a/stlib/geometry/__init__.py +++ b/stlib/geometry/__init__.py @@ -1 +1 @@ -from __geometry__ import * \ No newline at end of file +from .__geometry__ import * \ No newline at end of file diff --git a/stlib/geometry/extract.py b/stlib/geometry/extract.py index ae9c97e69..8edba4c65 100644 --- a/stlib/geometry/extract.py +++ b/stlib/geometry/extract.py @@ -31,11 +31,11 @@ def generateAttribute(self, parent : Geometry): elif self.fromElemType == ElementType.HEXA: tmn.addObject("Hexa2QuadTopologicalMapping", input=fromLink, output=tmn.container.linkpath) - self.positions = tmn.container.position.linkpath + self.position = tmn.container.position.linkpath self.edges = tmn.container.edges.linkpath self.triangles = tmn.container.triangles.linkpath self.quads = tmn.container.quads.linkpath - self.hexaedra = tmn.container.hexaedra.linkpath + self.hexahedra = tmn.container.hexahedra.linkpath self.tetrahedra = tmn.container.tetras.linkpath diff --git a/stlib/geometry/file.py b/stlib/geometry/file.py index 1c15597df..e2e4a60d9 100644 --- a/stlib/geometry/file.py +++ b/stlib/geometry/file.py @@ -7,7 +7,7 @@ @dataclasses.dataclass class FileInternalDataProvider(InternalDataProvider): - filename : str + filename : str = "mesh/cube.obj" def __post_init__(self, **kwargs): InternalDataProvider.__init__(self,**kwargs) @@ -16,16 +16,16 @@ def generateAttribute(self, parent : Geometry): loadMesh(parent, self.filename) - self.positions = parent.loader.position.linkpath - self.edges = parent.loader.edges.linkpath - self.triangles = parent.loader.triangles.linkpath - self.quads = parent.loader.quads.linkpath - self.hexaedra = parent.loader.hexaedra.linkpath - self.tetrahedra = parent.loader.tetras.linkpath + self.position = str(parent.loader.position.linkpath) + self.edges = str(parent.loader.edges.linkpath) + self.triangles = str(parent.loader.triangles.linkpath) + self.quads = str(parent.loader.quads.linkpath) + self.hexahedra = str(parent.loader.hexahedra.linkpath) + self.tetrahedra = str(parent.loader.tetras.linkpath) class FileParameters(GeometryParameters): def __init__(self, filename, dynamicTopology = False, elementType : ElementType = None ): - GeometryParameters.__init__(data = FileInternalDataProvider(filename), dynamicTopology = dynamicTopology, elementType = elementType) + GeometryParameters.__init__(self,data = FileInternalDataProvider(filename), dynamicTopology = dynamicTopology, elementType = elementType) diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index 4a41d2736..c7c8d6308 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -1,15 +1,16 @@ from stlib.core.basePrefab import BasePrefab from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any from stlib.geometry import Geometry, GeometryParameters +from stlib.geometry.file import FileParameters from Sofa.Core import Object @dataclasses.dataclass class VisualParameters(BaseParameters): - color : Optional[list[float]] - texture : Optional[str] + color : Optional[list[float]] = None + texture : Optional[str] = None - geometry : GeometryParameters - addMapping : Optional[Callable] + geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) + addMapping : Optional[Callable] = None class Visual(BasePrefab): @@ -26,3 +27,14 @@ def __init__(self, params: VisualParameters): if params.addMapping is not None: params.addMapping(self) + @staticmethod + def getParameters(**kwargs) -> VisualParameters: + return VisualParameters(**kwargs) + + +def createScene(root): + + # Create a visual from a mesh file + params = Visual.getParameters() + params.geometry = FileParameters(filename="mesh/cube.obj") + root.add(Visual, params) From 423a484b6fa7b3aca21b34778446b4b07240b74d Mon Sep 17 00:00:00 2001 From: bakpaul Date: Tue, 15 Apr 2025 16:13:44 +0200 Subject: [PATCH 23/41] Finished collisions --- splib/core/enum_types.py | 6 +++- splib/mechanics/collision_model.py | 33 +++++++++++++------- stlib/prefabs/collision.py | 50 ++++++++++++++++++++++++++---- stlib/prefabs/visual.py | 3 -- 4 files changed, 70 insertions(+), 22 deletions(-) diff --git a/splib/core/enum_types.py b/splib/core/enum_types.py index dc1b5e864..dbce47e14 100644 --- a/splib/core/enum_types.py +++ b/splib/core/enum_types.py @@ -23,7 +23,11 @@ class ConstraintType(Enum): WEAK = 2 LAGRANGIAN = 3 - +class CollisionPrimitive(Enum): + POINTS = 1 + LINES = 2 + TRIANGLES = 3 + SPHERES = 4 class ElementType(Enum): POINTS = 1 diff --git a/splib/mechanics/collision_model.py b/splib/mechanics/collision_model.py index 40bf0c1d8..ad8effade 100644 --- a/splib/mechanics/collision_model.py +++ b/splib/mechanics/collision_model.py @@ -1,18 +1,27 @@ from splib.core.node_wrapper import ReusableMethod from splib.core.utils import DEFAULT_VALUE -from splib.core.enum_types import ElementType +from splib.core.enum_types import CollisionPrimitive @ReusableMethod -def addCollisionModels(node,points=False, edges=False,triangles=False, spheres=False,tetrahedron=False,selfCollision=DEFAULT_VALUE, proximity=DEFAULT_VALUE, group=DEFAULT_VALUE, contactStiffness=DEFAULT_VALUE, contactFriction=DEFAULT_VALUE,spheresRadius=DEFAULT_VALUE,**kwargs): - if(points): - node.addObject("PointCollisionModel",name="PointCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, **kwargs) - if(edges): - node.addObject("LineCollisionModel",name="EdgeCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) - if(triangles): - node.addObject("TriangleCollisionModel",name="TriangleCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) - if(spheres): - node.addObject("SphereCollisionModel",name="SphereCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, radius=spheresRadius, **kwargs) - if(tetrahedron): - node.addObject("TetrahedronCollisionModel",name="TetraCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) +def addCollisionModels(node, primitive : CollisionPrimitive, topology=DEFAULT_VALUE, selfCollision=DEFAULT_VALUE, proximity=DEFAULT_VALUE, group=DEFAULT_VALUE, contactStiffness=DEFAULT_VALUE, contactFriction=DEFAULT_VALUE,spheresRadius=DEFAULT_VALUE,**kwargs): + match primitive: + case CollisionPrimitive.POINTS: + node.addObject("PointCollisionModel",name="PointCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, **kwargs) + return + case CollisionPrimitive.LINES: + node.addObject("LineCollisionModel",name="EdgeCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) + return + case CollisionPrimitive.TRIANGLES: + node.addObject("TriangleCollisionModel",name="TriangleCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) + return + case CollisionPrimitive.SPHERES: + node.addObject("SphereCollisionModel",name="SphereCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, radius=spheresRadius, **kwargs) + return + case _: + return + + #Cube and tetra are missing. + + \ No newline at end of file diff --git a/stlib/prefabs/collision.py b/stlib/prefabs/collision.py index bb73fe4bc..28915629b 100644 --- a/stlib/prefabs/collision.py +++ b/stlib/prefabs/collision.py @@ -1,18 +1,56 @@ from stlib.core.basePrefab import BasePrefab from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any -from stlib.geometry import GeometryParameters +from stlib.geometry import Geometry, GeometryParameters +from stlib.geometry.file import FileParameters +from splib.core.enum_types import CollisionPrimitive +from splib.core.utils import DEFAULT_VALUE +from splib.mechanics.collision_model import addCollisionModels from Sofa.Core import Object @dataclasses.dataclass class CollisionParameters(BaseParameters): - primitives : list[Any] #TODO: add enum type in splib + primitives : list[CollisionPrimitive] = dataclasses.field(default_factory = lambda :[CollisionPrimitive.POINTS]) - geometry : GeometryParameters - addMapping : Callable + selfCollision : Optional[bool] = DEFAULT_VALUE + proximity : Optional[float] = DEFAULT_VALUE + group : Optional[int] = DEFAULT_VALUE + contactStiffness : Optional[float] = DEFAULT_VALUE + contactFriction : Optional[float] = DEFAULT_VALUE + spheresRadius : Optional[float] = DEFAULT_VALUE + geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) + addMapping : Optional[Callable] = None -class Collision(BasePrefab): - def __init__(self, params: GeometryParameters): +class Collision(BasePrefab): + def __init__(self, params: CollisionParameters): BasePrefab.__init__(self, params) + geom = self.add(Geometry, params.geometry) + + self.addObject("MechanicalObject",template="Vec3", position=f"@{params.geometry.name}/container.position") + for primitive in params.primitives: + addCollisionModels(self,primitive, + topology=f"@{params.geometry.name}/container", + selfCollision=params.selfCollision, + proximity=params.proximity, + group=params.group, + contactStiffness=params.contactStiffness, + contactFriction=params.contactFriction, + spheresRadius=params.spheresRadius, + **params.kwargs) + + if params.addMapping is not None: + params.addMapping(self) + + @staticmethod + def getParameters(**kwargs) -> CollisionParameters: + return CollisionParameters(**kwargs) + + +def createScene(root): + + # Create a visual from a mesh file + params = Collision.getParameters() + params.geometry = FileParameters(filename="mesh/cube.obj") + root.add(Collision, params) diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index c7c8d6308..2bf61ed95 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -14,9 +14,6 @@ class VisualParameters(BaseParameters): class Visual(BasePrefab): - container : Object # This should be more specialized into the right SOFA type - modifier : Optional[Object] - def __init__(self, params: VisualParameters): BasePrefab.__init__(self, params) From c97bfa36be4a4f83987b6fdffaaf360a46290d36 Mon Sep 17 00:00:00 2001 From: EulalieCoevoet Date: Mon, 2 Jun 2025 14:55:37 +0200 Subject: [PATCH 24/41] [stlib-splib] work on prefabs: cleaning (#501) * [stlib-splib] cleaning * revert collisionModel --- splib/core/enum_types.py | 41 ++++++++++++++-------------- splib/mechanics/collision_model.py | 21 +++++++++----- splib/mechanics/hyperelasticity.py | 2 +- splib/mechanics/linear_elasticity.py | 6 ++-- splib/topology/dynamic.py | 8 +++--- stlib/geometry/__geometry__.py | 5 +--- stlib/geometry/extract.py | 35 ++++++++++++++++-------- stlib/geometry/file.py | 12 ++++---- stlib/prefabs/collision.py | 21 +++++++------- stlib/prefabs/visual.py | 13 ++++++++- 10 files changed, 96 insertions(+), 68 deletions(-) diff --git a/splib/core/enum_types.py b/splib/core/enum_types.py index dbce47e14..568245734 100644 --- a/splib/core/enum_types.py +++ b/splib/core/enum_types.py @@ -5,34 +5,33 @@ class ConstitutiveLaw(Enum): HYPERELASTIC = 2 class ODEType(Enum): - EXPLICIT = 1 - IMPLICIT = 2 + EXPLICIT = 1 + IMPLICIT = 2 class SolverType(Enum): - DIRECT = 1 - ITERATIVE = 2 + DIRECT = 1 + ITERATIVE = 2 class MappingType(Enum): - BARYCENTRIC = 1 - IDENTITY = 2 - RIGID = 3 - + BARYCENTRIC = 1 + IDENTITY = 2 + RIGID = 3 class ConstraintType(Enum): - PROJECTIVE = 1 - WEAK = 2 - LAGRANGIAN = 3 + PROJECTIVE = 1 + WEAK = 2 + LAGRANGIAN = 3 class CollisionPrimitive(Enum): - POINTS = 1 - LINES = 2 - TRIANGLES = 3 - SPHERES = 4 + POINTS = 1 + LINES = 2 + TRIANGLES = 3 + SPHERES = 4 class ElementType(Enum): - POINTS = 1 - EDGES = 2 - TRIANGLES = 3 - QUAD = 4 - TETRA = 5 - HEXA = 6 + POINTS = 1 + EDGES = 2 + TRIANGLES = 3 + QUADS = 4 + TETRAHEDRONS = 5 + HEXAHEDRONS = 6 diff --git a/splib/mechanics/collision_model.py b/splib/mechanics/collision_model.py index ad8effade..e1abdf457 100644 --- a/splib/mechanics/collision_model.py +++ b/splib/mechanics/collision_model.py @@ -3,21 +3,28 @@ from splib.core.enum_types import CollisionPrimitive - @ReusableMethod -def addCollisionModels(node, primitive : CollisionPrimitive, topology=DEFAULT_VALUE, selfCollision=DEFAULT_VALUE, proximity=DEFAULT_VALUE, group=DEFAULT_VALUE, contactStiffness=DEFAULT_VALUE, contactFriction=DEFAULT_VALUE,spheresRadius=DEFAULT_VALUE,**kwargs): +def addCollisionModels(node, primitive : CollisionPrimitive, + topology=DEFAULT_VALUE, + selfCollision=DEFAULT_VALUE, + proximity=DEFAULT_VALUE, + group=DEFAULT_VALUE, + contactStiffness=DEFAULT_VALUE, + contactFriction=DEFAULT_VALUE, + spheresRadius=DEFAULT_VALUE, + **kwargs): match primitive: case CollisionPrimitive.POINTS: - node.addObject("PointCollisionModel",name="PointCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, **kwargs) + node.addObject("PointCollisionModel", name="PointCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, **kwargs) return - case CollisionPrimitive.LINES: - node.addObject("LineCollisionModel",name="EdgeCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) + case CollisionPrimitive.LINES: + node.addObject("LineCollisionModel", name="EdgeCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, **kwargs) return case CollisionPrimitive.TRIANGLES: - node.addObject("TriangleCollisionModel",name="TriangleCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) + node.addObject("TriangleCollisionModel", name="TriangleCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs) return case CollisionPrimitive.SPHERES: - node.addObject("SphereCollisionModel",name="SphereCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, radius=spheresRadius, **kwargs) + node.addObject("SphereCollisionModel", name="SphereCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, radius=spheresRadius, **kwargs) return case _: return diff --git a/splib/mechanics/hyperelasticity.py b/splib/mechanics/hyperelasticity.py index 1e79c4206..27c306358 100644 --- a/splib/mechanics/hyperelasticity.py +++ b/splib/mechanics/hyperelasticity.py @@ -6,7 +6,7 @@ @ReusableMethod def addHyperelasticity(node,elem:ElementType,materialName=DEFAULT_VALUE, parameterSet=DEFAULT_VALUE, matrixRegularization=DEFAULT_VALUE,**kwargs): match elem: - case ElementType.TETRA: + case ElementType.TETRAHEDRONS: node.addObject("TetrahedronHyperelasticityFEMForceField",name="constitutiveLaw", materialName=materialName, parameterSet=parameterSet, matrixRegularization=matrixRegularization, **kwargs) return case _: diff --git a/splib/mechanics/linear_elasticity.py b/splib/mechanics/linear_elasticity.py index 8675afe19..33de61895 100644 --- a/splib/mechanics/linear_elasticity.py +++ b/splib/mechanics/linear_elasticity.py @@ -12,13 +12,13 @@ def addLinearElasticity(node,elem:ElementType,youngModulus=DEFAULT_VALUE, poisso case ElementType.TRIANGLES: node.addObject("TriangleFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) return - case ElementType.QUAD: + case ElementType.QUADS: node.addObject("QuadBendingFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) return - case ElementType.TETRA: + case ElementType.TETRAHEDRONS: node.addObject("TetrahedronFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) return - case ElementType.HEXA: + case ElementType.HEXAHEDRONS: node.addObject("HexahedronFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) return case _: diff --git a/splib/topology/dynamic.py b/splib/topology/dynamic.py index b5a829c39..c84bd1850 100644 --- a/splib/topology/dynamic.py +++ b/splib/topology/dynamic.py @@ -53,15 +53,15 @@ def addDynamicTopology(node,type:ElementType,**kwargs): case ElementType.TRIANGLES: addTriangleTopology(node,**kwargs) return - case ElementType.QUAD: + case ElementType.QUADS: addQuadTopology(node,**kwargs) return - case ElementType.TETRA: + case ElementType.TETRAHEDRONS: addTetrahedronTopology(node,**kwargs) return - case ElementType.HEXA: + case ElementType.HEXAHEDRONS: addHexahedronTopology(node,**kwargs) return case _: - print('Topology type should be one of the following : "ElementType.POINTS, ElementType.EDGES, ElementType.TRIANGLES, ElementType.QUAD, ElementType.TETRA, ElementType.HEXA" ') + print('Topology type should be one of the following : "ElementType.POINTS, ElementType.EDGES, ElementType.TRIANGLES, ElementType.QUADS, ElementType.TETRAHEDRONS, ElementType.HEXAHEDRONS" ') return diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py index 32ed6b610..8b63a20cc 100644 --- a/stlib/geometry/__geometry__.py +++ b/stlib/geometry/__geometry__.py @@ -33,11 +33,8 @@ class GeometryParameters(BaseParameters): dynamicTopology : bool = False - - - class Geometry(BasePrefab): - container : Object # This should be more specialized into the right SOFA type + container : Object # This should be more specialized into the right SOFA type modifier : Optional[Object] params : GeometryParameters diff --git a/stlib/geometry/extract.py b/stlib/geometry/extract.py index 8edba4c65..0e7c38c1a 100644 --- a/stlib/geometry/extract.py +++ b/stlib/geometry/extract.py @@ -8,27 +8,32 @@ class ExtractInternalDataProvider(InternalDataProvider): - destElemType : ElementType - fromElemType : ElementType + destElementType : ElementType + fromElemenType : ElementType fromNodeName : str + def __init__(self, destElementType : ElementType, fromElementType : ElementType, fromNodeName : str): + self.destElementType = destElementType, + self.fromElementType = fromElementType, + self.fromNodeName = fromNodeName + def __post_init__(self): - if(not (self.fromElemType == ElementType.TETRA and self.destElemType == ElementType.TRIANGLES) - and not (self.fromElemType == ElementType.HEXA and self.destElemType == ElementType.QUAD) ): - raise ValueError("Only configuration possible are 'Tetra to Triangle' and 'Hexa to quad'") + if(not (self.fromElementType == ElementType.TETRAHEDRONS and self.destElementType == ElementType.TRIANGLES) + and not (self.fromElementType == ElementType.HEXAHEDRONS and self.destElementType == ElementType.QUADS) ): + raise ValueError("Only configuration possible are 'Tetrahedrons to Triangles' and 'Hexahedrons to Quads'") InternalDataProvider.__init__(self) def generateAttribute(self, parent : Geometry): - tmn = parent.addChild("topologicalMappingNode") + tmn = parent.addChild("TopologicalMappingNode") #TODO: Specify somewhere in the doc that this should only be used for mapped topologies that extract parent topology surface fromLink = parent.parents[0].parents[0].getChild(self.fromNodeName).container.linkpath - addDynamicTopology(tmn, type=self.destElemType) - if self.fromElemType == ElementType.TETRA: + addDynamicTopology(tmn, type=self.destElementType) + if self.fromElementType == ElementType.TETRAHEDRONS: tmn.addObject("Tetra2TriangleTopologicalMapping", input=fromLink, output=tmn.container.linkpath) - elif self.fromElemType == ElementType.HEXA: + elif self.fromElementType == ElementType.HEXAHEDRONS: tmn.addObject("Hexa2QuadTopologicalMapping", input=fromLink, output=tmn.container.linkpath) self.position = tmn.container.position.linkpath @@ -41,6 +46,14 @@ def generateAttribute(self, parent : Geometry): class ExtractParameters(GeometryParameters): - def __init__(self, fromGeometry : GeometryParameters, destElementType : ElementType, dynamicTopology = False, ): - GeometryParameters.__init__(data = ExtractInternalDataProvider(destElemType = destElementType, fromElemType = fromGeometry.elementType,fromNodeName = fromGeometry.name), dynamicTopology = dynamicTopology, elementType = destElementType) + def __init__(self, + fromGeometry : GeometryParameters, + destElementType : ElementType, + dynamicTopology = False, ): + GeometryParameters.__init__(self, + data = ExtractInternalDataProvider(destElementType = destElementType, + fromElementType = fromGeometry.elementType, + fromNodeName = fromGeometry.name), + dynamicTopology = dynamicTopology, + elementType = destElementType) diff --git a/stlib/geometry/file.py b/stlib/geometry/file.py index e2e4a60d9..70738b02f 100644 --- a/stlib/geometry/file.py +++ b/stlib/geometry/file.py @@ -12,9 +12,8 @@ class FileInternalDataProvider(InternalDataProvider): def __post_init__(self, **kwargs): InternalDataProvider.__init__(self,**kwargs) - def generateAttribute(self, parent : Geometry): - - loadMesh(parent, self.filename) + def generateAttribute(self, parent : Geometry): + loadMesh(parent, self.filename) self.position = str(parent.loader.position.linkpath) self.edges = str(parent.loader.edges.linkpath) @@ -26,6 +25,9 @@ def generateAttribute(self, parent : Geometry): class FileParameters(GeometryParameters): - def __init__(self, filename, dynamicTopology = False, elementType : ElementType = None ): - GeometryParameters.__init__(self,data = FileInternalDataProvider(filename), dynamicTopology = dynamicTopology, elementType = elementType) + def __init__(self, filename, dynamicTopology = False, elementType : ElementType = None ): + GeometryParameters.__init__(self, + data = FileInternalDataProvider(filename), + dynamicTopology = dynamicTopology, + elementType = elementType) diff --git a/stlib/prefabs/collision.py b/stlib/prefabs/collision.py index 28915629b..8ed1f8a67 100644 --- a/stlib/prefabs/collision.py +++ b/stlib/prefabs/collision.py @@ -9,14 +9,10 @@ @dataclasses.dataclass class CollisionParameters(BaseParameters): - primitives : list[CollisionPrimitive] = dataclasses.field(default_factory = lambda :[CollisionPrimitive.POINTS]) + primitives : list[CollisionPrimitive] = dataclasses.field(default_factory = lambda :[CollisionPrimitive.TRIANGLES]) selfCollision : Optional[bool] = DEFAULT_VALUE - proximity : Optional[float] = DEFAULT_VALUE group : Optional[int] = DEFAULT_VALUE - contactStiffness : Optional[float] = DEFAULT_VALUE - contactFriction : Optional[float] = DEFAULT_VALUE - spheresRadius : Optional[float] = DEFAULT_VALUE geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) addMapping : Optional[Callable] = None @@ -28,16 +24,12 @@ def __init__(self, params: CollisionParameters): geom = self.add(Geometry, params.geometry) - self.addObject("MechanicalObject",template="Vec3", position=f"@{params.geometry.name}/container.position") + self.addObject("MechanicalObject", template="Vec3", position=f"@{params.geometry.name}/container.position") for primitive in params.primitives: - addCollisionModels(self,primitive, + addCollisionModels(self, primitive, topology=f"@{params.geometry.name}/container", selfCollision=params.selfCollision, - proximity=params.proximity, group=params.group, - contactStiffness=params.contactStiffness, - contactFriction=params.contactFriction, - spheresRadius=params.spheresRadius, **params.kwargs) if params.addMapping is not None: @@ -50,7 +42,14 @@ def getParameters(**kwargs) -> CollisionParameters: def createScene(root): + root.addObject("VisualStyle", displayFlags="showCollisionModels") + # Create a visual from a mesh file params = Collision.getParameters() params.geometry = FileParameters(filename="mesh/cube.obj") + # Expert parameters + params.kwargs = { + "contactStiffness": 100.0, + "contactFriction": 0.5 + } root.add(Collision, params) diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index 2bf61ed95..70790463e 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -1,7 +1,9 @@ from stlib.core.basePrefab import BasePrefab from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any from stlib.geometry import Geometry, GeometryParameters +from stlib.geometry.extract import ExtractParameters from stlib.geometry.file import FileParameters +from splib.core.enum_types import ElementType from Sofa.Core import Object @dataclasses.dataclass @@ -32,6 +34,15 @@ def getParameters(**kwargs) -> VisualParameters: def createScene(root): # Create a visual from a mesh file - params = Visual.getParameters() + params = Visual.getParameters() + params.name = "VisualFromFile" params.geometry = FileParameters(filename="mesh/cube.obj") root.add(Visual, params) + + + # Create a visual from a mesh file + # params = Visual.getParameters() + # params.name = "ExtractedVisual" + # params.geometry = ExtractParameters(fromGeometry=FileParameters(filename="mesh/cube.vtk"), + # destElementType=ElementType.TRIANGLES) + # root.add(Visual, params) From 995d1b3befe1665b86dd5653f9431ef7ecb84e48 Mon Sep 17 00:00:00 2001 From: EulalieCoevoet Date: Mon, 2 Jun 2025 14:57:36 +0200 Subject: [PATCH 25/41] [stlib] adds README --- stlib/README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 stlib/README.md diff --git a/stlib/README.md b/stlib/README.md new file mode 100644 index 000000000..ff1881cd8 --- /dev/null +++ b/stlib/README.md @@ -0,0 +1,42 @@ +# STLIB + +## Terminology + +| Term | Description | +| -------------- | -------------------------------------------------------------- | +| Component* | Element of the scene hierarchy implementing a given behavior | +| ~~Object~~ | A deprecated synonym of a Component | +| Node* | Element of the scene hierarchy holding other Node (often refered as childs) or Objects | +| Data* | Attribute of a Component or a Node | +| Prefab | A Sofa.Node assembling of Objects and Nodes (a "fragment" of a scene) | +| Geometry | A prefab that describe shapes with their topologies (i.e a shape with its space descritization and its associated connectivity) | +| Entity | A physical prefab that represents real-world properties and behaviors used in a simulation. An entity should always have a geometry.| +| Parameters | Every prefab has a set of parameters. These parameters can contain data, links, callable or being composed of other parameters. Some of them can be optional. ~~Must inherit from `stlib.core.baseParameter.BaseParameter` and have a `@dataclasses.dataclass` decorator~~. Must have a `@stlib.parameters` decorator. | + +*Defined in SOFA documentation [here](https://www.sofa-framework.org/doc/using-sofa/terminology). + +## Concepts & Structure + +This library is structured to provide a set of _Prefabs_ that can be used to build complex simulations in SOFA. +Prefabs are reusable fragments of a scene that can be specified through Parameters. +We introduce two different concepts, Prefab and Parameters: +- Prefabs defining the logic of instantiation +- Parameters providing the information (e.g data, links, callable) needed by a Prefab for its own instantiation + +We introduce two types of Prefabs: +- __Geometry__: A prefab that describes shapes with their topologies (i.e a shape with its space discretization and its associated connectivity). +- __Entity__: A physical prefab that represents real-world properties and behaviors used in a simulation. An entity should always have a geometry. + +## Usage + +STLIB has been designed to suit the following levels of use: + +- __Beginners__: + - Create simple simulations using predefined Prefabs. + - Use the provided Prefabs and Parameters without needing to understand the underlying implementation. +- __Intermediate users__: + - Create more complex simulations by combining existing Prefabs. + - Redefine Parameters for their own usage. +- __Advanced users__: + - Create their own Prefabs from scratch or by extending the provided ones. + - Enrich the library with new Prefabs and Parameters. From 01531ca5de7792e8973f23e58a0e5cd20ff127b4 Mon Sep 17 00:00:00 2001 From: EulalieCoevoet Date: Mon, 2 Jun 2025 14:58:51 +0200 Subject: [PATCH 26/41] [stlib] minor changes from discussion --- stlib/geometry/extract.py | 13 +++++++------ stlib/prefabs/collision.py | 12 +++++++++--- stlib/prefabs/visual.py | 7 +++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/stlib/geometry/extract.py b/stlib/geometry/extract.py index 0e7c38c1a..b7b17cbc6 100644 --- a/stlib/geometry/extract.py +++ b/stlib/geometry/extract.py @@ -29,6 +29,7 @@ def generateAttribute(self, parent : Geometry): tmn = parent.addChild("TopologicalMappingNode") #TODO: Specify somewhere in the doc that this should only be used for mapped topologies that extract parent topology surface + fromLink = parent.parents[0].parents[0].getChild(self.fromNodeName).container.linkpath addDynamicTopology(tmn, type=self.destElementType) if self.fromElementType == ElementType.TETRAHEDRONS: @@ -47,13 +48,13 @@ def generateAttribute(self, parent : Geometry): class ExtractParameters(GeometryParameters): def __init__(self, - fromGeometry : GeometryParameters, - destElementType : ElementType, + sourceParameters : GeometryParameters, + destinationType : ElementType, dynamicTopology = False, ): GeometryParameters.__init__(self, - data = ExtractInternalDataProvider(destElementType = destElementType, - fromElementType = fromGeometry.elementType, - fromNodeName = fromGeometry.name), + data = ExtractInternalDataProvider(destElementType = sourceParameters, + fromElementType = destinationType, + fromNodeName = destinationType.name), dynamicTopology = dynamicTopology, - elementType = destElementType) + elementType = destinationType) diff --git a/stlib/prefabs/collision.py b/stlib/prefabs/collision.py index 8ed1f8a67..7e8704c48 100644 --- a/stlib/prefabs/collision.py +++ b/stlib/prefabs/collision.py @@ -46,10 +46,16 @@ def createScene(root): # Create a visual from a mesh file params = Collision.getParameters() + params.group = 1 params.geometry = FileParameters(filename="mesh/cube.obj") # Expert parameters params.kwargs = { - "contactStiffness": 100.0, - "contactFriction": 0.5 + "TriangleCollisionModel":{"contactStiffness": 100.0, "contactFriction": 0.5} } - root.add(Collision, params) + collision = root.add(Collision, params) + + # OR set the parameters post creation + collision.TriangleCollisionModel.contactStiffness = 100.0 + collision.TriangleCollisionModel.contactFriction = 0.5 + collision.TriangleCollisionModel.set(contactStiffness=100.0, contactFriction=0.5) # we have information of what is possible + collision.TriangleCollisionModel.set({"contactStiffness": 100.0, "contactFriction": 0.5}) # we can do n'importe quoi diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index 70790463e..7752a2db0 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -39,10 +39,9 @@ def createScene(root): params.geometry = FileParameters(filename="mesh/cube.obj") root.add(Visual, params) - - # Create a visual from a mesh file + # # Create a visual from a node # params = Visual.getParameters() # params.name = "ExtractedVisual" - # params.geometry = ExtractParameters(fromGeometry=FileParameters(filename="mesh/cube.vtk"), - # destElementType=ElementType.TRIANGLES) + # params.geometry = ExtractParameters(sourceParameters=FileParameters(filename="mesh/cube.vtk"), + # destinationType=ElementType.TRIANGLES) # root.add(Visual, params) From fc1404ae6863fee2cf40f07dffc61bbbc9a9eaa3 Mon Sep 17 00:00:00 2001 From: EulalieCoevoet Date: Tue, 3 Jun 2025 12:54:04 +0200 Subject: [PATCH 27/41] [splib] updates entity --- splib/Testing.py | 4 +- splib/core/enum_types.py | 7 ++- stlib/README.md | 8 ++++ stlib/entities/__entity__.py | 50 ++++++++++----------- stlib/entities/deformable/__deformable__.py | 21 +++++---- stlib/prefabs/behavior.py | 15 +++++++ stlib/prefabs/collision.py | 16 ++++--- 7 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 stlib/prefabs/behavior.py diff --git a/splib/Testing.py b/splib/Testing.py index fc31093cf..ee0f83097 100644 --- a/splib/Testing.py +++ b/splib/Testing.py @@ -57,10 +57,10 @@ def createScene(rootNode): addImplicitODE(SimulatedLiver1) addLinearSolver(SimulatedLiver1,iterative=False, template="CompressedRowSparseMatrixMat3x3") loadMesh(SimulatedLiver1,filename="mesh/liver.msh") - addDynamicTopology(SimulatedLiver1,type=ElementType.TETRA,source="@meshLoader") + addDynamicTopology(SimulatedLiver1,type=ElementType.TETRAHEDRONS,source="@meshLoader") SimulatedLiver1.addObject("MechanicalObject",name="mstate", template='Vec3d') SimulatedLiver1.addObject("LinearSolverConstraintCorrection",name="constraintCorrection") - addLinearElasticity(SimulatedLiver1,ElementType.TETRA, poissonRatio="0.3", youngModulus="3000", method='large') + addLinearElasticity(SimulatedLiver1,ElementType.TETRAHEDRONS, poissonRatio="0.3", youngModulus="3000", method='large') addMass(SimulatedLiver1,template='Vec3d',massDensity="2") addFixation(SimulatedLiver1,ConstraintType.PROJECTIVE,boxROIs=[0, 3, 0, 2, 5, 2]) diff --git a/splib/core/enum_types.py b/splib/core/enum_types.py index 568245734..9f5f05c09 100644 --- a/splib/core/enum_types.py +++ b/splib/core/enum_types.py @@ -1,7 +1,7 @@ from enum import Enum class ConstitutiveLaw(Enum): - LINEAR_COROT = 1 + ELASTIC = 1 HYPERELASTIC = 2 class ODEType(Enum): @@ -35,3 +35,8 @@ class ElementType(Enum): QUADS = 4 TETRAHEDRONS = 5 HEXAHEDRONS = 6 + +class StateType(Enum): + VEC3 = 3 + VEC1 = 1 + RIGID = 7 \ No newline at end of file diff --git a/stlib/README.md b/stlib/README.md index ff1881cd8..2cb668d87 100644 --- a/stlib/README.md +++ b/stlib/README.md @@ -40,3 +40,11 @@ STLIB has been designed to suit the following levels of use: - __Advanced users__: - Create their own Prefabs from scratch or by extending the provided ones. - Enrich the library with new Prefabs and Parameters. + +## Available Parameters + +Each Prefab comes with a set of Parameters, these have been selected when the following criteria are met: +- Data which corresponds to an intraseque property of the Prefab, required for its instantiation +- Data which does not have a default value +- Data which cannot be inferred +- Data which are commun to all possible usage of the Prefab diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index 705a154ab..f65de8df6 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -1,25 +1,29 @@ from stlib.core.baseParameters import BaseParameters +from stlib.prefabs.collision import CollisionParameters, Collision +from stlib.prefabs.visual import VisualParameters, Visual +from stlib.prefabs.behavior import Behavior, BehaviorParameters +from stlib.geometry import Geometry import dataclasses from typing import Callable, Optional, overload, Any from stlib.geometry import GeometryParameters import Sofa + @dataclasses.dataclass class EntityParameters(BaseParameters): name = "Entity" # addSimulation : Callable = Simulation - # addCollisionModel : Callable = CollisionModel - # addVisualModel : Callable = VisualModel - - - #setConstitutiveLaw # : Callable = addBidule #setBoundaryCondition #: Callable = addBidule + + addCollision : Optional[Callable] = lambda x : Collision(CollisionParameters()) + addVisual : Optional[Callable] = lambda x : Visual(VisualParameters()) + geometry : GeometryParameters # mechanical : dict = dataclasses.field(default_factory=dict) - # collision : CollisionModel.Parameters = CollisionModel.Parameters() - # visual : VisualModelParameters = VisualModelParameters() + collision : Optional[CollisionParameters] = None + visual : Optional[VisualParameters] = None # simulation : SimulationParameters = SimulationParameters() @@ -27,9 +31,10 @@ class EntityParameters(BaseParameters): class Entity(Sofa.Core.BaseEntity): # A simulated object - simulation : Simulation - visual : VisualModel - collision : CollisionModel + behavior : Behavior + visual : Visual + collision : Collision + geometry : Geometry parameters : EntityParameters @@ -42,21 +47,16 @@ def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): self.parameters = parameters - self.addMechanicalModel(**parameters.mechanical) - # ???? - self.addSimulation(parameters=parameters.simulation) - self.addVisualModel(parameters=parameters.visual) - self.addCollisionModel() - - def addMechanicalModel(self, **kwargs): - self.addObject("MechanicalObject", **kwargs) + self.add(Geometry, self.parameters.geometry) + self.addBehavior(parameters=parameters.behavior) + self.addVisual(parameters=parameters.visual) + self.addCollision(parameters=parameters.collision) - # ???? - def addSimulation(self, **kwargs): - self.parameters.addSimulation(self, **kwargs) + def addBehavior(self, **kwargs): + self.parameters.addBehavior(self, **kwargs) - def addVisualModel(self, **kwargs): - self.parameters.addVisualModel(self, **kwargs) + def addVisual(self, **kwargs): + self.parameters.addVisual(self, **kwargs) - def addCollisionModel(self): - pass + def addCollision(self, **kwargs): + self.parameters.addCollision(self, **kwargs) diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 3f024b230..05b12d603 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -1,10 +1,15 @@ -from stlib.entities import Entity +from stlib.entities import Entity, EntityParameters from stlib.entities.deformable.__parameters__ import DeformableParameters -from stlib.geometry import Geometry from stlib.prefabs.visual import Visual -from stlib.prefabs.collision import Collision +from stlib.mixins.collision import CollisionMixin +from splib.core.enum_types import ConstitutiveLaw + +class DeformableParameters(EntityParameters): + + constitutiveLaw : ConstitutiveLaw = ConstitutiveLaw.ELASTIC + +class Deformable(CollisionMixin, Entity): -class Deformable(Entity): params : DeformableParameters @staticmethod @@ -13,10 +18,10 @@ def getParameters(**kwargs) -> DeformableParameters: def __init__(self, params : DeformableParameters, **kwargs): - Entity.__init__(self, **kwargs) - self.add(Geometry, params.geometry) + Entity.__init__(self, **kwargs) + self.__addConstitutiveLaw__() - self.add(Collision, params.collision) + self.addCollision(params.collision) #@customizable @@ -28,5 +33,5 @@ def __addConstitutiveLaw__(self): #@customizable def __addVisual__(self): #Extract surface and add identity mapping - self.add(Visual, params.visual) + self.add(Visual, self.params.visual) diff --git a/stlib/prefabs/behavior.py b/stlib/prefabs/behavior.py new file mode 100644 index 000000000..70fe932db --- /dev/null +++ b/stlib/prefabs/behavior.py @@ -0,0 +1,15 @@ +from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any +from splib.core.enum_types import StateType + + +@dataclasses.dataclass +class BehaviorParameters(BaseParameters): + name: str = "Behavior" + + stateType: StateType = StateType.VEC3 + massDensity: float # TODO : discuss with Hugo massDensity/totalMass + + + + + diff --git a/stlib/prefabs/collision.py b/stlib/prefabs/collision.py index 7e8704c48..8ab59559b 100644 --- a/stlib/prefabs/collision.py +++ b/stlib/prefabs/collision.py @@ -12,7 +12,9 @@ class CollisionParameters(BaseParameters): primitives : list[CollisionPrimitive] = dataclasses.field(default_factory = lambda :[CollisionPrimitive.TRIANGLES]) selfCollision : Optional[bool] = DEFAULT_VALUE + bothSide : Optional[bool] = DEFAULT_VALUE group : Optional[int] = DEFAULT_VALUE + contactDistance : Optional[float] = DEFAULT_VALUE geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) addMapping : Optional[Callable] = None @@ -49,13 +51,13 @@ def createScene(root): params.group = 1 params.geometry = FileParameters(filename="mesh/cube.obj") # Expert parameters - params.kwargs = { - "TriangleCollisionModel":{"contactStiffness": 100.0, "contactFriction": 0.5} - } + # params.kwargs = { + # "TriangleCollisionModel":{"contactStiffness": 100.0, "contactFriction": 0.5} + # } collision = root.add(Collision, params) # OR set the parameters post creation - collision.TriangleCollisionModel.contactStiffness = 100.0 - collision.TriangleCollisionModel.contactFriction = 0.5 - collision.TriangleCollisionModel.set(contactStiffness=100.0, contactFriction=0.5) # we have information of what is possible - collision.TriangleCollisionModel.set({"contactStiffness": 100.0, "contactFriction": 0.5}) # we can do n'importe quoi + # collision.TriangleCollisionModel.contactStiffness = 100.0 + # collision.TriangleCollisionModel.contactFriction = 0.5 + # collision.TriangleCollisionModel.set(contactStiffness=100.0, contactFriction=0.5) # we have information of what is possible + # collision.TriangleCollisionModel.set({"contactStiffness": 100.0, "contactFriction": 0.5}) # we can do n'importe quoi From dd230b9bf2c182743735face02d6af1c50602cc8 Mon Sep 17 00:00:00 2001 From: hugtalbot Date: Tue, 3 Jun 2025 13:10:40 +0200 Subject: [PATCH 28/41] update README --- stlib/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stlib/README.md b/stlib/README.md index 2cb668d87..fd71c6d1c 100644 --- a/stlib/README.md +++ b/stlib/README.md @@ -10,10 +10,11 @@ | Data* | Attribute of a Component or a Node | | Prefab | A Sofa.Node assembling of Objects and Nodes (a "fragment" of a scene) | | Geometry | A prefab that describe shapes with their topologies (i.e a shape with its space descritization and its associated connectivity) | -| Entity | A physical prefab that represents real-world properties and behaviors used in a simulation. An entity should always have a geometry.| +| Entity | A physical prefab that represents real-world properties and behaviors used in a simulation. An entity should always have a geometry but includes neither a linear solver nor an integration scheme.| | Parameters | Every prefab has a set of parameters. These parameters can contain data, links, callable or being composed of other parameters. Some of them can be optional. ~~Must inherit from `stlib.core.baseParameter.BaseParameter` and have a `@dataclasses.dataclass` decorator~~. Must have a `@stlib.parameters` decorator. | -*Defined in SOFA documentation [here](https://www.sofa-framework.org/doc/using-sofa/terminology). +\*Defined in SOFA documentation [here](https://www.sofa-framework.org/doc/using-sofa/terminology). + ## Concepts & Structure @@ -25,7 +26,8 @@ We introduce two different concepts, Prefab and Parameters: We introduce two types of Prefabs: - __Geometry__: A prefab that describes shapes with their topologies (i.e a shape with its space discretization and its associated connectivity). -- __Entity__: A physical prefab that represents real-world properties and behaviors used in a simulation. An entity should always have a geometry. +- __Entity__: A physical prefab that represents real-world properties and behaviors used in a simulation. An entity should always have a geometry but includes neither a linear solver nor an integration scheme. + ## Usage From 57efa1dc0383656d3a6bff975c485cf013594223 Mon Sep 17 00:00:00 2001 From: hugtalbot Date: Tue, 3 Jun 2025 13:34:51 +0200 Subject: [PATCH 29/41] add init in prefab --- stlib/prefabs/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 stlib/prefabs/__init__.py diff --git a/stlib/prefabs/__init__.py b/stlib/prefabs/__init__.py new file mode 100644 index 000000000..2d2771cfc --- /dev/null +++ b/stlib/prefabs/__init__.py @@ -0,0 +1 @@ +__all__ = ["behavior","collision","visual"] From 938f05b779a7950c8bd5f994daa8d3c31ae89162 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Tue, 3 Jun 2025 13:57:08 +0200 Subject: [PATCH 30/41] Add first implem of entity --- stlib/entities/__entity__.py | 43 +++++++++++++++++++++++------------- stlib/prefabs/behavior.py | 4 ++++ stlib/prefabs/collision.py | 3 --- stlib/prefabs/visual.py | 1 - 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index f65de8df6..19e6be231 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -3,9 +3,11 @@ from stlib.prefabs.visual import VisualParameters, Visual from stlib.prefabs.behavior import Behavior, BehaviorParameters from stlib.geometry import Geometry +from stlib.geometry.extract import ExtractParameters import dataclasses from typing import Callable, Optional, overload, Any from stlib.geometry import GeometryParameters +from splib.core.enum_types import StateType import Sofa @@ -13,18 +15,16 @@ class EntityParameters(BaseParameters): name = "Entity" - # addSimulation : Callable = Simulation - #setConstitutiveLaw # : Callable = addBidule - #setBoundaryCondition #: Callable = addBidule + template : StateType + ### QUID addCollision : Optional[Callable] = lambda x : Collision(CollisionParameters()) addVisual : Optional[Callable] = lambda x : Visual(VisualParameters()) geometry : GeometryParameters - # mechanical : dict = dataclasses.field(default_factory=dict) + behavior : BehaviorParameters collision : Optional[CollisionParameters] = None visual : Optional[VisualParameters] = None - # simulation : SimulationParameters = SimulationParameters() @@ -47,16 +47,29 @@ def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): self.parameters = parameters - self.add(Geometry, self.parameters.geometry) - self.addBehavior(parameters=parameters.behavior) - self.addVisual(parameters=parameters.visual) - self.addCollision(parameters=parameters.collision) + self.geometry = self.add(Geometry, self.parameters.geometry) - def addBehavior(self, **kwargs): - self.parameters.addBehavior(self, **kwargs) + ### Check compatilibility of Behavior + if self.parameters.behavior.stateType != self.parameters.template: + print("WARNING: imcompatibility between templates of entity and behavior") + self.parameters.behavior.stateType = self.parameters.template - def addVisual(self, **kwargs): - self.parameters.addVisual(self, **kwargs) + self.behavior = self.add(Behavior,self.parameters.behavior) + + if self.parameters.collision is not None: + self.collision = self.add(Collision,self.parameters.collision) + self.addMapping(self.parameters.collision, self.collision) + + + if self.parameters.visual is not None: + self.visual = self.add(Visual,self.parameters.visual) + self.addMapping(self.parameters.visual, self.visual) - def addCollision(self, **kwargs): - self.parameters.addCollision(self, **kwargs) + def addMapping(self, destParameter, destPrefab): + if( self.parameters.template == StateType.VEC3): + if isinstance(destParameter.geometry,ExtractParameters): + destPrefab.addObject("IdentityMapping", input="@../behavior/", output="@.", template='Vec3,Vec3') + else : + destPrefab.addObject("BarycentricMapping", input="@../behavior/", output="@.", template='Vec3,Vec3') + else: + destPrefab.addObject("RigidMapping", input="@../behavior", output="@.", template='Rigid3,Vec3') diff --git a/stlib/prefabs/behavior.py b/stlib/prefabs/behavior.py index 70fe932db..5e5960e5e 100644 --- a/stlib/prefabs/behavior.py +++ b/stlib/prefabs/behavior.py @@ -1,5 +1,6 @@ from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any from splib.core.enum_types import StateType +from stlib.core.basePrefab import BasePrefab @dataclasses.dataclass @@ -13,3 +14,6 @@ class BehaviorParameters(BaseParameters): +class Behavior(BasePrefab): + def __init__(self, params: BehaviorParameters): + BasePrefab.__init__(self, params) diff --git a/stlib/prefabs/collision.py b/stlib/prefabs/collision.py index 8ab59559b..218c0c469 100644 --- a/stlib/prefabs/collision.py +++ b/stlib/prefabs/collision.py @@ -17,7 +17,6 @@ class CollisionParameters(BaseParameters): contactDistance : Optional[float] = DEFAULT_VALUE geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) - addMapping : Optional[Callable] = None class Collision(BasePrefab): @@ -34,8 +33,6 @@ def __init__(self, params: CollisionParameters): group=params.group, **params.kwargs) - if params.addMapping is not None: - params.addMapping(self) @staticmethod def getParameters(**kwargs) -> CollisionParameters: diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index 7752a2db0..2326b5d19 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -12,7 +12,6 @@ class VisualParameters(BaseParameters): texture : Optional[str] = None geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) - addMapping : Optional[Callable] = None class Visual(BasePrefab): From 592c13d96d4eb68c827877fcc39586ed9ee33fff Mon Sep 17 00:00:00 2001 From: hugtalbot Date: Tue, 3 Jun 2025 16:16:11 +0200 Subject: [PATCH 31/41] before the train 3/3 --- splib/core/enum_types.py | 10 ++- splib/mechanics/mass.py | 1 + stlib/entities/__entity__.py | 26 ++++--- stlib/entities/deformable/__deformable__.py | 76 +++++++++++++++------ stlib/entities/deformable/__parameters__.py | 18 ----- stlib/prefabs/behavior.py | 19 ------ stlib/prefabs/material.py | 26 +++++++ 7 files changed, 105 insertions(+), 71 deletions(-) delete mode 100644 stlib/entities/deformable/__parameters__.py delete mode 100644 stlib/prefabs/behavior.py create mode 100644 stlib/prefabs/material.py diff --git a/splib/core/enum_types.py b/splib/core/enum_types.py index 9f5f05c09..eec743076 100644 --- a/splib/core/enum_types.py +++ b/splib/core/enum_types.py @@ -39,4 +39,12 @@ class ElementType(Enum): class StateType(Enum): VEC3 = 3 VEC1 = 1 - RIGID = 7 \ No newline at end of file + RIGID = 7 + + def __str__(self): + if self == StateType.VEC3: + return "Vec3" + if self == StateType.VEC1: + return "Vec1" + if self == StateType.RIGID: + return "Rigid3" diff --git a/splib/mechanics/mass.py b/splib/mechanics/mass.py index e3fe68fe3..2dc8dbdf7 100644 --- a/splib/mechanics/mass.py +++ b/splib/mechanics/mass.py @@ -3,6 +3,7 @@ from splib.core.enum_types import ElementType +# TODO : use the massDensity only and deduce totalMass if necessary from it + volume @ReusableMethod def addMass(node,template,totalMass=DEFAULT_VALUE,massDensity=DEFAULT_VALUE,lumping=DEFAULT_VALUE,**kwargs): if (not isDefault(totalMass)) and (not isDefault(massDensity)) : diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index 19e6be231..6304b8d0e 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -1,7 +1,7 @@ from stlib.core.baseParameters import BaseParameters from stlib.prefabs.collision import CollisionParameters, Collision from stlib.prefabs.visual import VisualParameters, Visual -from stlib.prefabs.behavior import Behavior, BehaviorParameters +from stlib.prefabs.material import Material, MaterialParameters from stlib.geometry import Geometry from stlib.geometry.extract import ExtractParameters import dataclasses @@ -22,7 +22,7 @@ class EntityParameters(BaseParameters): addVisual : Optional[Callable] = lambda x : Visual(VisualParameters()) geometry : GeometryParameters - behavior : BehaviorParameters + material : MaterialParameters collision : Optional[CollisionParameters] = None visual : Optional[VisualParameters] = None @@ -31,7 +31,7 @@ class EntityParameters(BaseParameters): class Entity(Sofa.Core.BaseEntity): # A simulated object - behavior : Behavior + material : Material visual : Visual collision : Collision geometry : Geometry @@ -49,12 +49,12 @@ def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): self.geometry = self.add(Geometry, self.parameters.geometry) - ### Check compatilibility of Behavior - if self.parameters.behavior.stateType != self.parameters.template: - print("WARNING: imcompatibility between templates of entity and behavior") - self.parameters.behavior.stateType = self.parameters.template + ### Check compatilibility of Material + if self.parameters.material.stateType != self.parameters.template: + print("WARNING: imcompatibility between templates of both the entity and the material") + self.parameters.material.stateType = self.parameters.template - self.behavior = self.add(Behavior,self.parameters.behavior) + self.material = self.add(Material,self.parameters.material) if self.parameters.collision is not None: self.collision = self.add(Collision,self.parameters.collision) @@ -65,11 +65,15 @@ def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): self.visual = self.add(Visual,self.parameters.visual) self.addMapping(self.parameters.visual, self.visual) + def addMapping(self, destParameter, destPrefab): + + templateString = f'{self.parameters.template},{destParameter.template}' + if( self.parameters.template == StateType.VEC3): if isinstance(destParameter.geometry,ExtractParameters): - destPrefab.addObject("IdentityMapping", input="@../behavior/", output="@.", template='Vec3,Vec3') + destPrefab.addObject("IdentityMapping", input="@../material/", output="@.", template=templateString) else : - destPrefab.addObject("BarycentricMapping", input="@../behavior/", output="@.", template='Vec3,Vec3') + destPrefab.addObject("BarycentricMapping", input="@../material/", output="@.", template=templateString) else: - destPrefab.addObject("RigidMapping", input="@../behavior", output="@.", template='Rigid3,Vec3') + destPrefab.addObject("RigidMapping", input="@../material", output="@.", template=templateString) diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 05b12d603..007bc64f0 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -1,37 +1,69 @@ from stlib.entities import Entity, EntityParameters -from stlib.entities.deformable.__parameters__ import DeformableParameters +from stlib.prefabs.material import Material, MaterialParameters from stlib.prefabs.visual import Visual -from stlib.mixins.collision import CollisionMixin -from splib.core.enum_types import ConstitutiveLaw +from splib.core.enum_types import ConstitutiveLaw, ElementType +from splib.mechanics.linear_elasticity import * +from splib.mechanics.hyperelasticity import * +from splib.mechanics.mass import addMass -class DeformableParameters(EntityParameters): - constitutiveLaw : ConstitutiveLaw = ConstitutiveLaw.ELASTIC +class DeformableBehaviorParameters(MaterialParameters): -class Deformable(CollisionMixin, Entity): + constitutiveLawType : ConstitutiveLaw + elementType : ElementType + parameters : list[float] - params : DeformableParameters + def addMaterial(self, node): - @staticmethod - def getParameters(**kwargs) -> DeformableParameters: - return DeformableParameters(**kwargs) + addMass(node, node.stateType, massDensity=node.massDensity, lumping=node.massLumping) + + # TODO : change this with inheritance + if(self.constitutiveLawType == ConstitutiveLaw.HYPERELASTIC): + addHyperelasticity(node,self.elementType, self.parameters) + else: + addLinearElasticity(node,self.elementType, self.parameters[0], self.parameters[1]) + + +# class Deformable(Entity): + +# params : DeformableParameters + +# @staticmethod +# def getParameters(**kwargs) -> DeformableParameters: +# return DeformableParameters(**kwargs) - def __init__(self, params : DeformableParameters, **kwargs): - Entity.__init__(self, **kwargs) +# def __init__(self, params : DeformableParameters, **kwargs): +# Entity.__init__(self, **kwargs) - self.__addConstitutiveLaw__() - self.addCollision(params.collision) +# self.__addConstitutiveLaw__() +# self.addCollision(params.collision) - #@customizable - # Need generic way of defining paramaters (linear/hyper...) - def __addConstitutiveLaw__(self): - self.params.addConstitutiveLaw() +# #@customizable +# # Need generic way of defining paramaters (linear/hyper...) +# def __addConstitutiveLaw__(self): +# self.params.addConstitutiveLaw() - #@customizable - def __addVisual__(self): - #Extract surface and add identity mapping - self.add(Visual, self.params.visual) +# #@customizable +# def __addVisual__(self): +# #Extract surface and add identity mapping +# self.add(Visual, self.params.visual) + + + + + +def createScene(root) : + + from stlib.geometry.file import FileParameters + from stlib.geometry.extract import ExtractParameters + liverParameters = EntityParameters() + liverParameters.behavior = DeformableBehaviorParameters() + liverParameters.behavior.constitutiveLawType = ConstitutiveLaw.ELASTIC + liverParameters.behavior.parameters = [1000, 0.45] + liverParameters.geometry = FileParameters("liver.vtk") + liverParameters.visual = ExtractParameters() + myDeformableObject = root.add(Entity, liverParameters) \ No newline at end of file diff --git a/stlib/entities/deformable/__parameters__.py b/stlib/entities/deformable/__parameters__.py deleted file mode 100644 index d64c29f0e..000000000 --- a/stlib/entities/deformable/__parameters__.py +++ /dev/null @@ -1,18 +0,0 @@ -from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses -from stlib.geometry import GeometryParameters -from stlib.prefabs.visual import VisualParameters -from stlib.prefabs.collision import CollisionParameters - -@dataclasses.dataclass -class DeformableParameters(BaseParameters): - - geometry : GeometryParameters - # Add default value - visual : VisualParameters - collision : CollisionParameters - mass : float = float - - addConstitutiveLaw : Callable = lambda x: x - - def toDict(self): - return dataclasses.asdict(self) diff --git a/stlib/prefabs/behavior.py b/stlib/prefabs/behavior.py deleted file mode 100644 index 5e5960e5e..000000000 --- a/stlib/prefabs/behavior.py +++ /dev/null @@ -1,19 +0,0 @@ -from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any -from splib.core.enum_types import StateType -from stlib.core.basePrefab import BasePrefab - - -@dataclasses.dataclass -class BehaviorParameters(BaseParameters): - name: str = "Behavior" - - stateType: StateType = StateType.VEC3 - massDensity: float # TODO : discuss with Hugo massDensity/totalMass - - - - - -class Behavior(BasePrefab): - def __init__(self, params: BehaviorParameters): - BasePrefab.__init__(self, params) diff --git a/stlib/prefabs/material.py b/stlib/prefabs/material.py new file mode 100644 index 000000000..f3418cd1f --- /dev/null +++ b/stlib/prefabs/material.py @@ -0,0 +1,26 @@ +from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any +from splib.core.enum_types import StateType +from stlib.core.basePrefab import BasePrefab +from splib.mechanics.mass import addMass + +@dataclasses.dataclass +class MaterialParameters(BaseParameters): + name: str = "Material" + + stateType: StateType = StateType.VEC3 + massDensity: float + massLumping: bool + + addMaterial : Optional[Callable] = lambda node : addMass(node, node.stateType, massDensity=node.massDensity, lumping=node.massLumping) + + +# TODO : previously called Behavior +class Material(BasePrefab): + def __init__(self, params: MaterialParameters): + BasePrefab.__init__(self, params) + + self.params = params + self.addObject("MechanicalObject", name="States", template=str(self.params.stateType)) + + if(params.addMaterial is not None): + params.addMaterial(self) From ca864d89e0d30cb7878020f3a3e206513cb676b9 Mon Sep 17 00:00:00 2001 From: EulalieCoevoet Date: Tue, 3 Jun 2025 16:23:22 +0200 Subject: [PATCH 32/41] very last commit befor train --- stlib/entities/__init__.py | 2 +- stlib/entities/deformable/__deformable__.py | 4 ++-- stlib/prefabs/material.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/stlib/entities/__init__.py b/stlib/entities/__init__.py index a207a568f..bfad7c6c5 100644 --- a/stlib/entities/__init__.py +++ b/stlib/entities/__init__.py @@ -1 +1 @@ -from __entity__ import * \ No newline at end of file +from .__entity__ import * \ No newline at end of file diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 007bc64f0..229299a5c 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -58,12 +58,12 @@ def addMaterial(self, node): def createScene(root) : from stlib.geometry.file import FileParameters - from stlib.geometry.extract import ExtractParameters + # from stlib.geometry.extract import ExtractParameters liverParameters = EntityParameters() liverParameters.behavior = DeformableBehaviorParameters() liverParameters.behavior.constitutiveLawType = ConstitutiveLaw.ELASTIC liverParameters.behavior.parameters = [1000, 0.45] liverParameters.geometry = FileParameters("liver.vtk") - liverParameters.visual = ExtractParameters() + # liverParameters.visual = ExtractParameters() myDeformableObject = root.add(Entity, liverParameters) \ No newline at end of file diff --git a/stlib/prefabs/material.py b/stlib/prefabs/material.py index f3418cd1f..c4313244b 100644 --- a/stlib/prefabs/material.py +++ b/stlib/prefabs/material.py @@ -7,9 +7,10 @@ class MaterialParameters(BaseParameters): name: str = "Material" - stateType: StateType = StateType.VEC3 massDensity: float massLumping: bool + + stateType: StateType = StateType.VEC3 addMaterial : Optional[Callable] = lambda node : addMass(node, node.stateType, massDensity=node.massDensity, lumping=node.massLumping) From 1d8cdd3c90373dc94a8348002cedf9284fd355b7 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Tue, 3 Jun 2025 16:53:50 +0200 Subject: [PATCH 33/41] WIP --- splib/core/enum_types.py | 1 + stlib/entities/__entity__.py | 13 ++++++------- stlib/entities/deformable/__deformable__.py | 18 ++++++++++-------- stlib/prefabs/material.py | 8 +++++--- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/splib/core/enum_types.py b/splib/core/enum_types.py index eec743076..d27bd458c 100644 --- a/splib/core/enum_types.py +++ b/splib/core/enum_types.py @@ -48,3 +48,4 @@ def __str__(self): return "Vec1" if self == StateType.RIGID: return "Rigid3" + diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index 6304b8d0e..f17375329 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -9,26 +9,27 @@ from stlib.geometry import GeometryParameters from splib.core.enum_types import StateType import Sofa +from stlib.core.basePrefab import BasePrefab @dataclasses.dataclass class EntityParameters(BaseParameters): name = "Entity" - template : StateType + template : StateType = None ### QUID addCollision : Optional[Callable] = lambda x : Collision(CollisionParameters()) addVisual : Optional[Callable] = lambda x : Visual(VisualParameters()) - geometry : GeometryParameters - material : MaterialParameters + geometry : GeometryParameters = None + material : MaterialParameters = None collision : Optional[CollisionParameters] = None visual : Optional[VisualParameters] = None -class Entity(Sofa.Core.BaseEntity): +class Entity(BasePrefab): # A simulated object material : Material @@ -39,11 +40,9 @@ class Entity(Sofa.Core.BaseEntity): parameters : EntityParameters - def __init__(self, parent=None, parameters=EntityParameters(), **kwargs): + def __init__(self, parameters=EntityParameters(), **kwargs): Sofa.Core.Node.__init__(self, name=parameters.name) - if parent is not None: - parent.addChild(self) self.parameters = parameters diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 229299a5c..7213d6a74 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -1,7 +1,7 @@ from stlib.entities import Entity, EntityParameters from stlib.prefabs.material import Material, MaterialParameters from stlib.prefabs.visual import Visual -from splib.core.enum_types import ConstitutiveLaw, ElementType +from splib.core.enum_types import ConstitutiveLaw, ElementType, StateType from splib.mechanics.linear_elasticity import * from splib.mechanics.hyperelasticity import * from splib.mechanics.mass import addMass @@ -9,13 +9,13 @@ class DeformableBehaviorParameters(MaterialParameters): - constitutiveLawType : ConstitutiveLaw - elementType : ElementType - parameters : list[float] + constitutiveLawType : ConstitutiveLaw = None + elementType : ElementType = None + parameters : list[float] = None def addMaterial(self, node): - addMass(node, node.stateType, massDensity=node.massDensity, lumping=node.massLumping) + addMass(node, str(node.stateType), massDensity=node.massDensity, lumping=node.massLumping) # TODO : change this with inheritance if(self.constitutiveLawType == ConstitutiveLaw.HYPERELASTIC): @@ -61,9 +61,11 @@ def createScene(root) : # from stlib.geometry.extract import ExtractParameters liverParameters = EntityParameters() - liverParameters.behavior = DeformableBehaviorParameters() - liverParameters.behavior.constitutiveLawType = ConstitutiveLaw.ELASTIC - liverParameters.behavior.parameters = [1000, 0.45] + liverParameters.template = StateType.VEC3 + liverParameters.material = DeformableBehaviorParameters() + liverParameters.material.stateType = StateType.VEC3 + liverParameters.material.constitutiveLawType = ConstitutiveLaw.ELASTIC + liverParameters.material.parameters = [1000, 0.45] liverParameters.geometry = FileParameters("liver.vtk") # liverParameters.visual = ExtractParameters() myDeformableObject = root.add(Entity, liverParameters) \ No newline at end of file diff --git a/stlib/prefabs/material.py b/stlib/prefabs/material.py index c4313244b..cfc374286 100644 --- a/stlib/prefabs/material.py +++ b/stlib/prefabs/material.py @@ -1,5 +1,5 @@ from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any -from splib.core.enum_types import StateType +from splib.core.enum_types import * from stlib.core.basePrefab import BasePrefab from splib.mechanics.mass import addMass @@ -7,8 +7,8 @@ class MaterialParameters(BaseParameters): name: str = "Material" - massDensity: float - massLumping: bool + massDensity: float = 0 + massLumping: bool = False stateType: StateType = StateType.VEC3 @@ -21,6 +21,8 @@ def __init__(self, params: MaterialParameters): BasePrefab.__init__(self, params) self.params = params + + print(params.stateType.toString()) self.addObject("MechanicalObject", name="States", template=str(self.params.stateType)) if(params.addMaterial is not None): From bacb2589193fc964ecbb65f1033d7e09aeafa415 Mon Sep 17 00:00:00 2001 From: bakpaul Date: Tue, 3 Jun 2025 17:07:13 +0200 Subject: [PATCH 34/41] Retore original. Still crash due to wrong string conversion --- splib/core/enum_types.py | 2 +- stlib/prefabs/material.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/splib/core/enum_types.py b/splib/core/enum_types.py index d27bd458c..8968e2f5c 100644 --- a/splib/core/enum_types.py +++ b/splib/core/enum_types.py @@ -48,4 +48,4 @@ def __str__(self): return "Vec1" if self == StateType.RIGID: return "Rigid3" - + return 'Unknown' diff --git a/stlib/prefabs/material.py b/stlib/prefabs/material.py index cfc374286..3102f0e3c 100644 --- a/stlib/prefabs/material.py +++ b/stlib/prefabs/material.py @@ -22,7 +22,6 @@ def __init__(self, params: MaterialParameters): self.params = params - print(params.stateType.toString()) self.addObject("MechanicalObject", name="States", template=str(self.params.stateType)) if(params.addMaterial is not None): From 09f45c5985a5aed388502fb5b958f140a7349666 Mon Sep 17 00:00:00 2001 From: EulalieCoevoet Date: Fri, 13 Jun 2025 14:19:29 +0200 Subject: [PATCH 35/41] [stlib] Deformable Entity (#502) * working deformable entity * [splib] fixes match case * [stlib] WIP on extract.py --- splib/Testing.py | 4 +- splib/core/enum_types.py | 4 +- splib/mechanics/hyperelasticity.py | 2 +- splib/mechanics/linear_elasticity.py | 18 ++--- splib/mechanics/mass.py | 10 +-- splib/topology/dynamic.py | 12 +-- splib/topology/loader.py | 2 +- splib/topology/static.py | 2 +- stlib/README.md | 10 +-- stlib/core/baseParameters.py | 2 +- stlib/entities/__entity__.py | 45 ++++++----- stlib/entities/deformable/__deformable__.py | 85 ++++++++------------- stlib/geometry/__geometry__.py | 52 ++++++++----- stlib/geometry/extract.py | 69 ++++++++++------- stlib/geometry/file.py | 5 +- stlib/misc/entity.py | 2 +- stlib/misc/softrobots.py | 4 +- stlib/misc/test-1.py | 30 ++++---- stlib/misc/test2.py | 34 ++++----- stlib/prefabs/collision.py | 30 ++++---- stlib/prefabs/material.py | 26 ++++--- stlib/prefabs/visual.py | 37 ++++----- 22 files changed, 242 insertions(+), 243 deletions(-) diff --git a/splib/Testing.py b/splib/Testing.py index ee0f83097..146b4096b 100644 --- a/splib/Testing.py +++ b/splib/Testing.py @@ -57,10 +57,10 @@ def createScene(rootNode): addImplicitODE(SimulatedLiver1) addLinearSolver(SimulatedLiver1,iterative=False, template="CompressedRowSparseMatrixMat3x3") loadMesh(SimulatedLiver1,filename="mesh/liver.msh") - addDynamicTopology(SimulatedLiver1,type=ElementType.TETRAHEDRONS,source="@meshLoader") + addDynamicTopology(SimulatedLiver1,type=ElementType.TETRAHEDRA,source="@meshLoader") SimulatedLiver1.addObject("MechanicalObject",name="mstate", template='Vec3d') SimulatedLiver1.addObject("LinearSolverConstraintCorrection",name="constraintCorrection") - addLinearElasticity(SimulatedLiver1,ElementType.TETRAHEDRONS, poissonRatio="0.3", youngModulus="3000", method='large') + addLinearElasticity(SimulatedLiver1,ElementType.TETRAHEDRA, poissonRatio="0.3", youngModulus="3000", method='large') addMass(SimulatedLiver1,template='Vec3d',massDensity="2") addFixation(SimulatedLiver1,ConstraintType.PROJECTIVE,boxROIs=[0, 3, 0, 2, 5, 2]) diff --git a/splib/core/enum_types.py b/splib/core/enum_types.py index 8968e2f5c..5d4504311 100644 --- a/splib/core/enum_types.py +++ b/splib/core/enum_types.py @@ -33,8 +33,8 @@ class ElementType(Enum): EDGES = 2 TRIANGLES = 3 QUADS = 4 - TETRAHEDRONS = 5 - HEXAHEDRONS = 6 + TETRAHEDRA = 5 + HEXAHEDRA = 6 class StateType(Enum): VEC3 = 3 diff --git a/splib/mechanics/hyperelasticity.py b/splib/mechanics/hyperelasticity.py index 27c306358..fb255df41 100644 --- a/splib/mechanics/hyperelasticity.py +++ b/splib/mechanics/hyperelasticity.py @@ -6,7 +6,7 @@ @ReusableMethod def addHyperelasticity(node,elem:ElementType,materialName=DEFAULT_VALUE, parameterSet=DEFAULT_VALUE, matrixRegularization=DEFAULT_VALUE,**kwargs): match elem: - case ElementType.TETRAHEDRONS: + case ElementType.TETRAHEDRA: node.addObject("TetrahedronHyperelasticityFEMForceField",name="constitutiveLaw", materialName=materialName, parameterSet=parameterSet, matrixRegularization=matrixRegularization, **kwargs) return case _: diff --git a/splib/mechanics/linear_elasticity.py b/splib/mechanics/linear_elasticity.py index 33de61895..1e9b30290 100644 --- a/splib/mechanics/linear_elasticity.py +++ b/splib/mechanics/linear_elasticity.py @@ -4,22 +4,22 @@ @ReusableMethod -def addLinearElasticity(node,elem:ElementType,youngModulus=DEFAULT_VALUE, poissonRatio=DEFAULT_VALUE, method=DEFAULT_VALUE,**kwargs): - match elem: +def addLinearElasticity(node, elementType:ElementType, youngModulus=DEFAULT_VALUE, poissonRatio=DEFAULT_VALUE, method=DEFAULT_VALUE, **kwargs): + match elementType: case ElementType.EDGES: - node.addObject("BeamFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method, **kwargs) + node.addObject("BeamFEMForceField", name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method, **kwargs) return case ElementType.TRIANGLES: - node.addObject("TriangleFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) + node.addObject("TriangleFEMForceField", name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method, **kwargs) return case ElementType.QUADS: - node.addObject("QuadBendingFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) + node.addObject("QuadBendingFEMForceField", name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method, **kwargs) return - case ElementType.TETRAHEDRONS: - node.addObject("TetrahedronFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) + case ElementType.TETRAHEDRA: + node.addObject("TetrahedronFEMForceField", name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method, **kwargs) return - case ElementType.HEXAHEDRONS: - node.addObject("HexahedronFEMForceField",name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method,**kwargs) + case ElementType.HEXAHEDRA: + node.addObject("HexahedronFEMForceField", name="constitutiveLaw", youngModulus=youngModulus, poissonRatio=poissonRatio, method=method, **kwargs) return case _: print('Linear elasticity is only available for topology of type EDGES, TRIANGLES, QUADS, TETRAHEDRON, HEXAHEDRON') diff --git a/splib/mechanics/mass.py b/splib/mechanics/mass.py index 2dc8dbdf7..917fc9365 100644 --- a/splib/mechanics/mass.py +++ b/splib/mechanics/mass.py @@ -5,15 +5,15 @@ # TODO : use the massDensity only and deduce totalMass if necessary from it + volume @ReusableMethod -def addMass(node,template,totalMass=DEFAULT_VALUE,massDensity=DEFAULT_VALUE,lumping=DEFAULT_VALUE,**kwargs): +def addMass(node, template, totalMass=DEFAULT_VALUE, massDensity=DEFAULT_VALUE, lumping=DEFAULT_VALUE, **kwargs): if (not isDefault(totalMass)) and (not isDefault(massDensity)) : print("[warning] You defined the totalMass and the massDensity in the same time, only taking massDensity into account") kwargs.pop('massDensity') - if(template=="Rigid3"): - node.addObject("UniformMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) - else: - node.addObject("MeshMatrixMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) + # if(template=="Rigid3"): + node.addObject("UniformMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) + # else: + # node.addObject("MeshMatrixMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) diff --git a/splib/topology/dynamic.py b/splib/topology/dynamic.py index c84bd1850..1f18be29d 100644 --- a/splib/topology/dynamic.py +++ b/splib/topology/dynamic.py @@ -41,9 +41,8 @@ def addHexahedronTopology(node,position=DEFAULT_VALUE,edges=DEFAULT_VALUE,quads= node.addObject("HexahedronSetTopologyContainer", name="container", src=source, position=position, edges=edges, quads=quads, hexahedra=hexahedra, **kwargs) # node.addObject("HexahedronSetGeometryAlgorithms", name="algorithms",**kwargs) -def addDynamicTopology(node,type:ElementType,**kwargs): - - match type: +def addDynamicTopology(node, elementType:ElementType, **kwargs): + match elementType: case ElementType.POINTS: addPointTopology(node,**kwargs) return @@ -56,12 +55,13 @@ def addDynamicTopology(node,type:ElementType,**kwargs): case ElementType.QUADS: addQuadTopology(node,**kwargs) return - case ElementType.TETRAHEDRONS: + case ElementType.TETRAHEDRA: addTetrahedronTopology(node,**kwargs) return - case ElementType.HEXAHEDRONS: + case ElementType.HEXAHEDRA: addHexahedronTopology(node,**kwargs) return case _: - print('Topology type should be one of the following : "ElementType.POINTS, ElementType.EDGES, ElementType.TRIANGLES, ElementType.QUADS, ElementType.TETRAHEDRONS, ElementType.HEXAHEDRONS" ') + print('Topology type should be one of the following : "ElementType.POINTS, ElementType.EDGES, ElementType.TRIANGLES, ElementType.QUADS, ElementType.TETRAHEDRA, ElementType.HEXAHEDRA" ') return + \ No newline at end of file diff --git a/splib/topology/loader.py b/splib/topology/loader.py index b040491aa..c4f54b01f 100644 --- a/splib/topology/loader.py +++ b/splib/topology/loader.py @@ -13,7 +13,7 @@ def loadMesh(node,filename,**kwargs): elif splitedName[-1] == "sph": return node.addObject("SphereLoader", name="loader",filename=filename, **kwargs) else: - return node.addObject("Mesh"+splitedName[-1].upper()+"Loader", name="loader",filename=filename, **kwargs) + return node.addObject("Mesh"+splitedName[-1].upper()+"Loader", name="loader", filename=filename, **kwargs) else: print('[Error] : File extension ' + splitedName[-1] + ' not recognised.') diff --git a/splib/topology/static.py b/splib/topology/static.py index 2e49fcf95..591460425 100644 --- a/splib/topology/static.py +++ b/splib/topology/static.py @@ -2,6 +2,6 @@ from splib.core.utils import DEFAULT_VALUE @ReusableMethod -def addStaticTopology(node,source=DEFAULT_VALUE,**kwargs): +def addStaticTopology(node, source=DEFAULT_VALUE, **kwargs): node.addObject("MeshTopology", name="container", src=source, **kwargs) diff --git a/stlib/README.md b/stlib/README.md index fd71c6d1c..c4e7dd901 100644 --- a/stlib/README.md +++ b/stlib/README.md @@ -6,12 +6,12 @@ | -------------- | -------------------------------------------------------------- | | Component* | Element of the scene hierarchy implementing a given behavior | | ~~Object~~ | A deprecated synonym of a Component | -| Node* | Element of the scene hierarchy holding other Node (often refered as childs) or Objects | +| Node* | Element of the scene hierarchy holding other Nodes (often refered as childs) or Components | | Data* | Attribute of a Component or a Node | -| Prefab | A Sofa.Node assembling of Objects and Nodes (a "fragment" of a scene) | -| Geometry | A prefab that describe shapes with their topologies (i.e a shape with its space descritization and its associated connectivity) | -| Entity | A physical prefab that represents real-world properties and behaviors used in a simulation. An entity should always have a geometry but includes neither a linear solver nor an integration scheme.| -| Parameters | Every prefab has a set of parameters. These parameters can contain data, links, callable or being composed of other parameters. Some of them can be optional. ~~Must inherit from `stlib.core.baseParameter.BaseParameter` and have a `@dataclasses.dataclass` decorator~~. Must have a `@stlib.parameters` decorator. | +| Prefab | A Node assembling of Components and Nodes (a "fragment" of a scene) | +| Geometry | A Prefab that describes shapes with their topologies (i.e a shapes with their space descritization and their associated connectivity) | +| Entity | A physical Prefab that represents real-world properties and behaviors used in a simulation. An entity should always have a geometry but includes neither a linear solver nor an integration scheme.| +| Parameters | Every Prefab has a set of parameters. These parameters can contain data, links, callable or being composed of other parameters. Some of them can be optional. ~~Must inherit from `stlib.core.baseParameter.BaseParameter` and have a `@dataclasses.dataclass` decorator~~. Must have a `@stlib.parameters` decorator. | \*Defined in SOFA documentation [here](https://www.sofa-framework.org/doc/using-sofa/terminology). diff --git a/stlib/core/baseParameters.py b/stlib/core/baseParameters.py index 883b17b5b..be7a633b8 100644 --- a/stlib/core/baseParameters.py +++ b/stlib/core/baseParameters.py @@ -6,7 +6,7 @@ @dataclasses.dataclass class BaseParameters(object): - name : str = "object" + name : str = "Object" kwargs : dict = dataclasses.field(default_factory=dict) def toDict(self): diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index f17375329..f39d8b4ed 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -1,4 +1,5 @@ from stlib.core.baseParameters import BaseParameters +from stlib.core.basePrefab import BasePrefab from stlib.prefabs.collision import CollisionParameters, Collision from stlib.prefabs.visual import VisualParameters, Visual from stlib.prefabs.material import Material, MaterialParameters @@ -14,9 +15,9 @@ @dataclasses.dataclass class EntityParameters(BaseParameters): - name = "Entity" + name : str = "Entity" - template : StateType = None + stateType : StateType = StateType.VEC3 ### QUID addCollision : Optional[Callable] = lambda x : Collision(CollisionParameters()) @@ -41,38 +42,40 @@ class Entity(BasePrefab): def __init__(self, parameters=EntityParameters(), **kwargs): - Sofa.Core.Node.__init__(self, name=parameters.name) - - + BasePrefab.__init__(self, parameters) self.parameters = parameters self.geometry = self.add(Geometry, self.parameters.geometry) ### Check compatilibility of Material - if self.parameters.material.stateType != self.parameters.template: + if self.parameters.material.stateType != self.parameters.stateType: print("WARNING: imcompatibility between templates of both the entity and the material") - self.parameters.material.stateType = self.parameters.template + self.parameters.material.stateType = self.parameters.stateType - self.material = self.add(Material,self.parameters.material) + self.material = self.add(Material, self.parameters.material) + self.material.States.position.parent = self.geometry.container.position.linkpath if self.parameters.collision is not None: - self.collision = self.add(Collision,self.parameters.collision) - self.addMapping(self.parameters.collision, self.collision) - + self.collision = self.add(Collision, self.parameters.collision) + self.addMapping(self.collision) if self.parameters.visual is not None: - self.visual = self.add(Visual,self.parameters.visual) - self.addMapping(self.parameters.visual, self.visual) + self.visual = self.add(Visual, self.parameters.visual) + self.addMapping(self.visual) - def addMapping(self, destParameter, destPrefab): + def addMapping(self, destinationPrefab): - templateString = f'{self.parameters.template},{destParameter.template}' + template = f'{self.parameters.stateType},Vec3' # TODO: check that it is always true - if( self.parameters.template == StateType.VEC3): - if isinstance(destParameter.geometry,ExtractParameters): - destPrefab.addObject("IdentityMapping", input="@../material/", output="@.", template=templateString) - else : - destPrefab.addObject("BarycentricMapping", input="@../material/", output="@.", template=templateString) + if( self.parameters.stateType == StateType.VEC3): + destinationPrefab.addObject("BarycentricMapping", + input="@../Material/", + input_topology=destinationPrefab.geometry.container.linkpath, + output="@.", + template=template) else: - destPrefab.addObject("RigidMapping", input="@../material", output="@.", template=templateString) + destinationPrefab.addObject("RigidMapping", + input="@../Material", + output="@.", + template=template) diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 7213d6a74..1c73b2d9a 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -1,71 +1,46 @@ -from stlib.entities import Entity, EntityParameters -from stlib.prefabs.material import Material, MaterialParameters -from stlib.prefabs.visual import Visual -from splib.core.enum_types import ConstitutiveLaw, ElementType, StateType +from stlib.prefabs.material import MaterialParameters +from splib.core.enum_types import ConstitutiveLaw, ElementType +from stlib.core.baseParameters import Callable, Optional, dataclasses from splib.mechanics.linear_elasticity import * from splib.mechanics.hyperelasticity import * from splib.mechanics.mass import addMass +@dataclasses.dataclass class DeformableBehaviorParameters(MaterialParameters): - constitutiveLawType : ConstitutiveLaw = None - elementType : ElementType = None - parameters : list[float] = None - - def addMaterial(self, node): - - addMass(node, str(node.stateType), massDensity=node.massDensity, lumping=node.massLumping) + constitutiveLawType : ConstitutiveLaw = ConstitutiveLaw.ELASTIC + elementType : ElementType = ElementType.TETRAHEDRA + parameters : list[float] = dataclasses.field(default_factory=lambda: [1000, 0.45]) # young modulus, poisson ratio + def addDeformableMaterial(node): + addMass(node, node.parameters.stateType, massDensity=node.parameters.massDensity, lumping=node.parameters.massLumping) # TODO : change this with inheritance - if(self.constitutiveLawType == ConstitutiveLaw.HYPERELASTIC): - addHyperelasticity(node,self.elementType, self.parameters) + if(node.parameters.constitutiveLawType == ConstitutiveLaw.HYPERELASTIC): + addHyperelasticity(node, node.parameters.elementType, node.parameters.parameters, topology="@../Geometry/container") else: - addLinearElasticity(node,self.elementType, self.parameters[0], self.parameters[1]) - - -# class Deformable(Entity): - -# params : DeformableParameters - -# @staticmethod -# def getParameters(**kwargs) -> DeformableParameters: -# return DeformableParameters(**kwargs) - - -# def __init__(self, params : DeformableParameters, **kwargs): -# Entity.__init__(self, **kwargs) - -# self.__addConstitutiveLaw__() -# self.addCollision(params.collision) - - -# #@customizable -# # Need generic way of defining paramaters (linear/hyper...) -# def __addConstitutiveLaw__(self): -# self.params.addConstitutiveLaw() - - -# #@customizable -# def __addVisual__(self): -# #Extract surface and add identity mapping -# self.add(Visual, self.params.visual) - - + addLinearElasticity(node, node.parameters.elementType, node.parameters.parameters[0], node.parameters.parameters[1], topology="@../Geometry/container") + addMaterial : Optional[Callable] = addDeformableMaterial def createScene(root) : - + from stlib.entities import Entity, EntityParameters + from stlib.prefabs.visual import VisualParameters + from stlib.geometry.extract import ExtractParameters from stlib.geometry.file import FileParameters - # from stlib.geometry.extract import ExtractParameters - liverParameters = EntityParameters() - liverParameters.template = StateType.VEC3 - liverParameters.material = DeformableBehaviorParameters() - liverParameters.material.stateType = StateType.VEC3 - liverParameters.material.constitutiveLawType = ConstitutiveLaw.ELASTIC - liverParameters.material.parameters = [1000, 0.45] - liverParameters.geometry = FileParameters("liver.vtk") - # liverParameters.visual = ExtractParameters() - myDeformableObject = root.add(Entity, liverParameters) \ No newline at end of file + root.addObject("VisualStyle", displayFlags=["showBehavior"]) + + bunnyParameters = EntityParameters() + bunnyParameters.geometry = FileParameters(filename="mesh/Bunny.vtk") + bunnyParameters.geometry.elementType = ElementType.TETRAHEDRA # TODO: this is required by extract.py. Should it be done automatically in geometry.py ? + bunnyParameters.material = DeformableBehaviorParameters() + bunnyParameters.material.constitutiveLawType = ConstitutiveLaw.ELASTIC + bunnyParameters.material.parameters = [1000, 0.45] + bunnyParameters.visual = VisualParameters() + # bunnyParameters.visual.geometry = ExtractParameters(sourceParameters=bunnyParameters.geometry, + # destinationType=ElementType.TRIANGLES) + bunnyParameters.visual.geometry = FileParameters(filename="mesh/Bunny.stl") + bunnyParameters.visual.color = [1, 1, 1, 0.5] + bunny = root.add(Entity, bunnyParameters) \ No newline at end of file diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py index 8b63a20cc..085215da8 100644 --- a/stlib/geometry/__geometry__.py +++ b/stlib/geometry/__geometry__.py @@ -13,11 +13,11 @@ class Geometry(BasePrefab):... class InternalDataProvider(object): position : Any = None # Topology information - edges : Any = DEFAULT_VALUE - triangles : Any = DEFAULT_VALUE - quads : Any = DEFAULT_VALUE - tetrahedra : Any = DEFAULT_VALUE - hexahedra : Any = DEFAULT_VALUE + edges : Any = DEFAULT_VALUE + triangles : Any = DEFAULT_VALUE + quads : Any = DEFAULT_VALUE + tetrahedra : Any = DEFAULT_VALUE + hexahedra : Any = DEFAULT_VALUE def generateAttribute(self, parent : Geometry): pass @@ -25,6 +25,7 @@ def generateAttribute(self, parent : Geometry): @dataclasses.dataclass class GeometryParameters(BaseParameters): + name : str = "Geometry" # Type of the highest degree element elementType : Optional[ElementType] = None @@ -34,20 +35,33 @@ class GeometryParameters(BaseParameters): class Geometry(BasePrefab): - container : Object # This should be more specialized into the right SOFA type - modifier : Optional[Object] - - params : GeometryParameters - - def __init__(self, params: GeometryParameters): - BasePrefab.__init__(self, params) - self.params = params - if params.data is not None : - params.data.generateAttribute(self) - if(params.dynamicTopology): - if(params.elementType is not None): - addDynamicTopology(self, container = dataclasses.asdict(params.data)) + # container : Object # This should be more specialized into the right SOFA type + # modifier : Optional[Object] + + parameters : GeometryParameters + + def __init__(self, parameters: GeometryParameters): + + BasePrefab.__init__(self, parameters) + + self.parameters = parameters + + # Generate attribute (positions, edges, triangles, quads, tetrahedra, hexahedra) from the internal data provider + if parameters.data is not None : + parameters.data.generateAttribute(self) + if parameters.dynamicTopology : + if parameters.elementType is not None : + addDynamicTopology(self, container = dataclasses.asdict(parameters.data)) else: raise ValueError else: - addStaticTopology(self, container = dataclasses.asdict(params.data)) + addStaticTopology(self, + container = + { + "position": parameters.data.position, + "edges": parameters.data.edges, + "triangles": parameters.data.triangles, + "quads": parameters.data.quads, + "tetrahedra": parameters.data.tetrahedra, + "hexahedra": parameters.data.hexahedra + }) diff --git a/stlib/geometry/extract.py b/stlib/geometry/extract.py index b7b17cbc6..c20249666 100644 --- a/stlib/geometry/extract.py +++ b/stlib/geometry/extract.py @@ -4,45 +4,56 @@ from splib.topology.loader import loadMesh from splib.core.enum_types import ElementType +import Sofa from Sofa.Core import Node class ExtractInternalDataProvider(InternalDataProvider): - destElementType : ElementType - fromElemenType : ElementType - fromNodeName : str + destinationType : ElementType + sourceType : ElementType + sourceName : str - def __init__(self, destElementType : ElementType, fromElementType : ElementType, fromNodeName : str): - self.destElementType = destElementType, - self.fromElementType = fromElementType, - self.fromNodeName = fromNodeName + def __init__(self, destinationType : ElementType, sourceType : ElementType, sourceName : str): + self.destinationType = destinationType + self.sourceType = sourceType + self.sourceName = sourceName def __post_init__(self): - if(not (self.fromElementType == ElementType.TETRAHEDRONS and self.destElementType == ElementType.TRIANGLES) - and not (self.fromElementType == ElementType.HEXAHEDRONS and self.destElementType == ElementType.QUADS) ): - raise ValueError("Only configuration possible are 'Tetrahedrons to Triangles' and 'Hexahedrons to Quads'") + if(not (self.sourceType == ElementType.TETRAHEDRA and self.destinationType == ElementType.TRIANGLES) + and not (self.sourceType == ElementType.HEXAHEDRA and self.destinationType == ElementType.QUADS) ): + raise ValueError("Only configuration possible are 'Tetrahedra to Triangles' and 'Hexahedra to Quads'") InternalDataProvider.__init__(self) - def generateAttribute(self, parent : Geometry): - tmn = parent.addChild("TopologicalMappingNode") + node = parent.addChild("ExtractedGeometry") #TODO: Specify somewhere in the doc that this should only be used for mapped topologies that extract parent topology surface - - fromLink = parent.parents[0].parents[0].getChild(self.fromNodeName).container.linkpath - addDynamicTopology(tmn, type=self.destElementType) - if self.fromElementType == ElementType.TETRAHEDRONS: - tmn.addObject("Tetra2TriangleTopologicalMapping", input=fromLink, output=tmn.container.linkpath) - elif self.fromElementType == ElementType.HEXAHEDRONS: - tmn.addObject("Hexa2QuadTopologicalMapping", input=fromLink, output=tmn.container.linkpath) + # fromLink = parent.parents[0].parents[0].getChild(self.SourceName).container.linkpath + # TODO: the line above cannot work if the nodes and objects are not added to the graph prior the end of __init__() call + # !!! also, on a fail, nothing is added to the graph, which makes things harder to debug + # !!! also, does not work because of the function canCreate(), which checks the input (not yet created?) + # this is all related + fromLink = "@../../Geometry.container" # TODO: can we do better than this? + addDynamicTopology(node, elementType=self.sourceType) + if self.sourceType == ElementType.TETRAHEDRA: + node.addObject("Tetra2TriangleTopologicalMapping", input=fromLink, output=node.container.linkpath) + elif self.sourceType == ElementType.HEXAHEDRA: + node.addObject("Hexa2QuadTopologicalMapping", input=fromLink, output=node.container.linkpath) + else: + Sofa.msg_error("[stlib/geometry/exctrat.py]", "Element type: " + str(self.sourceType) + " not supported.") - self.position = tmn.container.position.linkpath - self.edges = tmn.container.edges.linkpath - self.triangles = tmn.container.triangles.linkpath - self.quads = tmn.container.quads.linkpath - self.hexahedra = tmn.container.hexahedra.linkpath - self.tetrahedra = tmn.container.tetras.linkpath + self.position = node.container.position.linkpath + if node.container.findData("edges") is not None: + self.edges = node.container.edges.linkpath + if node.container.findData("triangles") is not None: + self.triangles = node.container.triangles.linkpath + if node.container.findData("quads") is not None: + self.quads = node.container.quads.linkpath + if node.container.findData("hexahedra") is not None: + self.hexahedra = node.container.hexahedra.linkpath + if node.container.findData("tetras") is not None: + self.tetrahedra = node.container.tetras.linkpath @@ -50,11 +61,11 @@ class ExtractParameters(GeometryParameters): def __init__(self, sourceParameters : GeometryParameters, destinationType : ElementType, - dynamicTopology = False, ): + dynamicTopology : bool = False, ): GeometryParameters.__init__(self, - data = ExtractInternalDataProvider(destElementType = sourceParameters, - fromElementType = destinationType, - fromNodeName = destinationType.name), + data = ExtractInternalDataProvider(destinationType = destinationType, + sourceType = sourceParameters.elementType, + sourceName = sourceParameters.name), dynamicTopology = dynamicTopology, elementType = destinationType) diff --git a/stlib/geometry/file.py b/stlib/geometry/file.py index 70738b02f..cc51c077d 100644 --- a/stlib/geometry/file.py +++ b/stlib/geometry/file.py @@ -12,7 +12,7 @@ class FileInternalDataProvider(InternalDataProvider): def __post_init__(self, **kwargs): InternalDataProvider.__init__(self,**kwargs) - def generateAttribute(self, parent : Geometry): + def generateAttribute(self, parent : Geometry): loadMesh(parent, self.filename) self.position = str(parent.loader.position.linkpath) @@ -25,9 +25,10 @@ def generateAttribute(self, parent : Geometry): class FileParameters(GeometryParameters): + def __init__(self, filename, dynamicTopology = False, elementType : ElementType = None ): GeometryParameters.__init__(self, - data = FileInternalDataProvider(filename), + data = FileInternalDataProvider(filename=filename), dynamicTopology = dynamicTopology, elementType = elementType) diff --git a/stlib/misc/entity.py b/stlib/misc/entity.py index fbf702439..a3549b083 100644 --- a/stlib/misc/entity.py +++ b/stlib/misc/entity.py @@ -66,7 +66,7 @@ def addMapping(self, **kwargs): self.addObject("RigidMapping", name="mapping", **kwargs) class CollisionModel(Sofa.Core.BasePrefab): - def __init__(self, params, **kwargs): + def __init__(self, parameters, **kwargs): Sofa.Core.Node.__init__(self, **kwargs) class Parameters(object): diff --git a/stlib/misc/softrobots.py b/stlib/misc/softrobots.py index d8e7871a7..aca8ab50e 100644 --- a/stlib/misc/softrobots.py +++ b/stlib/misc/softrobots.py @@ -12,10 +12,10 @@ class Trunk(Sofa.Core.BasePrefab): body : Entity.Deformable cables : list [SoftRobots.Cable] - def __init__(self, params): + def __init__(self, parameters): body = Entity.Deformable() - for param in range(params.cables): + for param in range(parameters.cables): cables.append(SoftRobots.Cable(body, param)) class Parameters(object): diff --git a/stlib/misc/test-1.py b/stlib/misc/test-1.py index 289f1ff02..2b8987031 100644 --- a/stlib/misc/test-1.py +++ b/stlib/misc/test-1.py @@ -15,39 +15,39 @@ def createScene(root): #root.add(entity.Deformable) #root.addChild(entity2.Deformable(root)) - params = entity.Deformable.Parameters() - params.name = "Deformable2" - root.add(entity.Deformable, params=auto_load) + parameters = entity.Deformable.Parameters() + parameters.name = "Deformable2" + root.add(entity.Deformable, parameters=auto_load) #def addCustomVisual(self, **kwargs): # Rigid.addVisualModel( mapping={"toto":"in"} ) - #params = Entity.Parameters() - #params.addVisualModel = addCustomVisual - #root.add(Entity, params) + #parameters = Entity.Parameters() + #parameters.addVisualModel = addCustomVisual + #root.add(Entity, parameters) #  - #params = Rigid.new_parameters() - #params.mass = 4.5 - #root.add(Entity, params) + #parameters = Rigid.new_parameters() + #parameters.mass = 4.5 + #root.add(Entity, parameters) #root.add(Entity) - #params.addVisualModelOverride = addCustomVisual + #parameters.addVisualModelOverride = addCustomVisual ###  #Entity._addVisualModel = addCustomVisual - #root.add(Entity, params) + #root.add(Entity, parameters) #root.add(Entity.Rigid) #root.add(Entity.Deformable) #root.add(Entity) - #root.add(VisualModel, params) + #root.add(VisualModel, parameters) #root.add(VisualModel) - #params = Entity.Deformable.Parameters() - #params.visual = None - #a = root.add(Entity.Deformable, params) + #parameters = Entity.Deformable.Parameters() + #parameters.visual = None + #a = root.add(Entity.Deformable, parameters) return root \ No newline at end of file diff --git a/stlib/misc/test2.py b/stlib/misc/test2.py index 4dc12d147..cdebfad1c 100644 --- a/stlib/misc/test2.py +++ b/stlib/misc/test2.py @@ -19,7 +19,7 @@ def myAddObject(self : Sofa.Core.Node, tname, **kwargs): Sofa.Core.Node.addObject = myAddObject -def myAdd(self : Sofa.Core.Node, c, params = PrefabParameters(), **kwargs): +def myAdd(self : Sofa.Core.Node, c, parameters = PrefabParameters(), **kwargs): def findName(cname, node): """Compute a working unique name in the node""" rname = cname @@ -30,14 +30,14 @@ def findName(cname, node): return rname for k,v in kwargs.items(): - if hasattr(params, k): - setattr(params, k, v) + if hasattr(parameters, k): + setattr(parameters, k, v) - params = copy.copy(params) - if params.name in self.children: - params.name = findName(params.name, self) + parameters = copy.copy(parameters) + if parameters.name in self.children: + parameters.name = findName(parameters.name, self) - return c(parent = self, parameters=params) + return c(parent = self, parameters=parameters) Sofa.Core.Node.add = myAdd def createScene(root): @@ -47,19 +47,19 @@ def createScene(root): # self.addObject("EulerExplicitSolver", name="numericalintegration") # self.addObject("LinearSolver", name="numericalsolver", firstOrder=True) - params = EntityParameters() - params.simulation.iterations = 10 - params.simulation.integration["rayleighStiffness"] = 2.0 - params.addSimulation = entity.NONE + parameters = EntityParameters() + parameters.simulation.iterations = 10 + parameters.simulation.integration["rayleighStiffness"] = 2.0 + parameters.addSimulation = entity.NONE - params.mechanical["template"] = "Rigid3" + parameters.mechanical["template"] = "Rigid3" - #params.simulation.integration["rayleightStiffnessXXX"] = 2.0 + #parameters.simulation.integration["rayleightStiffnessXXX"] = 2.0 - #params.solver.kwargs["numericalintegration"] = { "firstOrder" : True } + #parameters.solver.kwargs["numericalintegration"] = { "firstOrder" : True } - root.add(Entity, params) - root.add(Entity, params) - root.add(Entity, params) + root.add(Entity, parameters) + root.add(Entity, parameters) + root.add(Entity, parameters) #root.add(Simulation, name="mySimulation") diff --git a/stlib/prefabs/collision.py b/stlib/prefabs/collision.py index 218c0c469..da603146f 100644 --- a/stlib/prefabs/collision.py +++ b/stlib/prefabs/collision.py @@ -9,6 +9,8 @@ @dataclasses.dataclass class CollisionParameters(BaseParameters): + name : str = "Collision" + primitives : list[CollisionPrimitive] = dataclasses.field(default_factory = lambda :[CollisionPrimitive.TRIANGLES]) selfCollision : Optional[bool] = DEFAULT_VALUE @@ -20,18 +22,18 @@ class CollisionParameters(BaseParameters): class Collision(BasePrefab): - def __init__(self, params: CollisionParameters): - BasePrefab.__init__(self, params) + def __init__(self, parameters: CollisionParameters): + BasePrefab.__init__(self, parameters) - geom = self.add(Geometry, params.geometry) + geom = self.add(Geometry, parameters.geometry) - self.addObject("MechanicalObject", template="Vec3", position=f"@{params.geometry.name}/container.position") - for primitive in params.primitives: + self.addObject("MechanicalObject", template="Vec3", position=f"@{parameters.geometry.name}/container.position") + for primitive in parameters.primitives: addCollisionModels(self, primitive, - topology=f"@{params.geometry.name}/container", - selfCollision=params.selfCollision, - group=params.group, - **params.kwargs) + topology=f"@{parameters.geometry.name}/container", + selfCollision=parameters.selfCollision, + group=parameters.group, + **parameters.kwargs) @staticmethod @@ -44,14 +46,14 @@ def createScene(root): root.addObject("VisualStyle", displayFlags="showCollisionModels") # Create a visual from a mesh file - params = Collision.getParameters() - params.group = 1 - params.geometry = FileParameters(filename="mesh/cube.obj") + parameters = Collision.getParameters() + parameters.group = 1 + parameters.geometry = FileParameters(filename="mesh/cube.obj") # Expert parameters - # params.kwargs = { + # parameters.kwargs = { # "TriangleCollisionModel":{"contactStiffness": 100.0, "contactFriction": 0.5} # } - collision = root.add(Collision, params) + collision = root.add(Collision, parameters) # OR set the parameters post creation # collision.TriangleCollisionModel.contactStiffness = 100.0 diff --git a/stlib/prefabs/material.py b/stlib/prefabs/material.py index 3102f0e3c..07009f421 100644 --- a/stlib/prefabs/material.py +++ b/stlib/prefabs/material.py @@ -1,28 +1,30 @@ from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any -from splib.core.enum_types import * +from splib.core.utils import defaultValueType, DEFAULT_VALUE, isDefault +from splib.core.enum_types import StateType + from stlib.core.basePrefab import BasePrefab from splib.mechanics.mass import addMass @dataclasses.dataclass class MaterialParameters(BaseParameters): - name: str = "Material" + name : str = "Material" - massDensity: float = 0 - massLumping: bool = False + massDensity : float = DEFAULT_VALUE + massLumping : bool = DEFAULT_VALUE - stateType: StateType = StateType.VEC3 + stateType : StateType = StateType.VEC3 - addMaterial : Optional[Callable] = lambda node : addMass(node, node.stateType, massDensity=node.massDensity, lumping=node.massLumping) + addMaterial : Optional[Callable] = lambda node : addMass(node, node.parameters.stateType, massDensity=node.parameters.massDensity, lumping=node.parameters.massLumping) # TODO : previously called Behavior class Material(BasePrefab): - def __init__(self, params: MaterialParameters): - BasePrefab.__init__(self, params) - self.params = params + parameters : MaterialParameters - self.addObject("MechanicalObject", name="States", template=str(self.params.stateType)) + def __init__(self, parameters: MaterialParameters): + BasePrefab.__init__(self, parameters) + self.parameters = parameters - if(params.addMaterial is not None): - params.addMaterial(self) + self.addObject("MechanicalObject", name="States", template=str(parameters.stateType)) + parameters.addMaterial(self) diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index 2326b5d19..bbc34bee1 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -1,29 +1,27 @@ from stlib.core.basePrefab import BasePrefab -from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any +from stlib.core.baseParameters import BaseParameters, Optional, dataclasses, Any from stlib.geometry import Geometry, GeometryParameters -from stlib.geometry.extract import ExtractParameters from stlib.geometry.file import FileParameters -from splib.core.enum_types import ElementType +from splib.core.utils import DEFAULT_VALUE from Sofa.Core import Object @dataclasses.dataclass class VisualParameters(BaseParameters): - color : Optional[list[float]] = None - texture : Optional[str] = None + name : str = "Visual" + + color : Optional[list[float]] = DEFAULT_VALUE + texture : Optional[str] = DEFAULT_VALUE geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) class Visual(BasePrefab): - def __init__(self, params: VisualParameters): - BasePrefab.__init__(self, params) - geom = self.add(Geometry, params.geometry) - # TODO : handle optional color, texture using DEFAULT_VALUE mechanism (as implemented by Paul) - self.addObject("OglModel", color=params.color, src=geom.container.linkpath) + def __init__(self, parameters: VisualParameters): + BasePrefab.__init__(self, parameters) - if params.addMapping is not None: - params.addMapping(self) + self.geometry = self.add(Geometry, parameters.geometry) + self.addObject("OglModel", color=parameters.color, src=self.geometry.container.linkpath) @staticmethod def getParameters(**kwargs) -> VisualParameters: @@ -33,14 +31,7 @@ def getParameters(**kwargs) -> VisualParameters: def createScene(root): # Create a visual from a mesh file - params = Visual.getParameters() - params.name = "VisualFromFile" - params.geometry = FileParameters(filename="mesh/cube.obj") - root.add(Visual, params) - - # # Create a visual from a node - # params = Visual.getParameters() - # params.name = "ExtractedVisual" - # params.geometry = ExtractParameters(sourceParameters=FileParameters(filename="mesh/cube.vtk"), - # destinationType=ElementType.TRIANGLES) - # root.add(Visual, params) + parameters = Visual.getParameters() + parameters.name = "LiverVisual" + parameters.geometry = FileParameters(filename="mesh/liver.obj") + root.add(Visual, parameters) \ No newline at end of file From d4b60425e94c47742d2ac0da14991f950b81290e Mon Sep 17 00:00:00 2001 From: bakpaul Date: Fri, 13 Jun 2025 15:03:47 +0200 Subject: [PATCH 36/41] Fix linkpath by introducing init method + make addDeformableMaterial private --- stlib/core/baseEntity.py | 2 ++ stlib/core/basePrefab.py | 12 ++++++++- stlib/entities/__entity__.py | 14 +++++----- stlib/entities/deformable/__deformable__.py | 4 +-- stlib/geometry/__geometry__.py | 29 +++++++++++---------- stlib/prefabs/collision.py | 16 +++++++----- stlib/prefabs/material.py | 7 ++--- stlib/prefabs/visual.py | 6 +++-- 8 files changed, 55 insertions(+), 35 deletions(-) diff --git a/stlib/core/baseEntity.py b/stlib/core/baseEntity.py index f93fc050e..1456436bb 100644 --- a/stlib/core/baseEntity.py +++ b/stlib/core/baseEntity.py @@ -7,3 +7,5 @@ class BaseEntity(Sofa.Core.Prefab): def __init__(self): Sofa.Core.Prefab.__init__(self) + + diff --git a/stlib/core/basePrefab.py b/stlib/core/basePrefab.py index 820e0e2b2..c2e101483 100644 --- a/stlib/core/basePrefab.py +++ b/stlib/core/basePrefab.py @@ -22,7 +22,10 @@ def findName(cname, node): if params.name in self.children: params.name = findName(params.name, self) - return self.addChild(typeName(params)) + pref = self.addChild(typeName(params)) + pref.init() + + return pref Sofa.Core.Node.add = addFromTypeName @@ -34,7 +37,14 @@ class BasePrefab(Sofa.Core.Node): def __init__(self, params: BasePrefabParameters): Sofa.Core.Node.__init__(self, name=params.name) + self.parameters = params + def init(self): + raise NotImplemented("To be overridden by child class") + + def localToGlobalCoordinates(pointCloudInput, pointCloudOutput): raise NotImplemented("Send an email to Damien, he will help you. Guaranteed :)") + + diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index f39d8b4ed..d8d5d8ca6 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -43,8 +43,9 @@ class Entity(BasePrefab): def __init__(self, parameters=EntityParameters(), **kwargs): BasePrefab.__init__(self, parameters) - self.parameters = parameters + + def init(self): self.geometry = self.add(Geometry, self.parameters.geometry) ### Check compatilibility of Material @@ -70,12 +71,13 @@ def addMapping(self, destinationPrefab): if( self.parameters.stateType == StateType.VEC3): destinationPrefab.addObject("BarycentricMapping", - input="@../Material/", - input_topology=destinationPrefab.geometry.container.linkpath, - output="@.", + output=destinationPrefab.linkpath, + output_topology=destinationPrefab.geometry.container.linkpath, + input=self.material.linkpath, + input_topology=self.geometry.container.linkpath, template=template) else: destinationPrefab.addObject("RigidMapping", - input="@../Material", - output="@.", + output=destinationPrefab.linkpath, + input=self.material.linkpath, template=template) diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 1c73b2d9a..458289c74 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -13,7 +13,7 @@ class DeformableBehaviorParameters(MaterialParameters): elementType : ElementType = ElementType.TETRAHEDRA parameters : list[float] = dataclasses.field(default_factory=lambda: [1000, 0.45]) # young modulus, poisson ratio - def addDeformableMaterial(node): + def __addDeformableMaterial(node): addMass(node, node.parameters.stateType, massDensity=node.parameters.massDensity, lumping=node.parameters.massLumping) # TODO : change this with inheritance if(node.parameters.constitutiveLawType == ConstitutiveLaw.HYPERELASTIC): @@ -21,7 +21,7 @@ def addDeformableMaterial(node): else: addLinearElasticity(node, node.parameters.elementType, node.parameters.parameters[0], node.parameters.parameters[1], topology="@../Geometry/container") - addMaterial : Optional[Callable] = addDeformableMaterial + addMaterial : Optional[Callable] = __addDeformableMaterial def createScene(root) : diff --git a/stlib/geometry/__geometry__.py b/stlib/geometry/__geometry__.py index 085215da8..10b5084b3 100644 --- a/stlib/geometry/__geometry__.py +++ b/stlib/geometry/__geometry__.py @@ -41,27 +41,28 @@ class Geometry(BasePrefab): parameters : GeometryParameters def __init__(self, parameters: GeometryParameters): - BasePrefab.__init__(self, parameters) - self.parameters = parameters + + + def init(self): # Generate attribute (positions, edges, triangles, quads, tetrahedra, hexahedra) from the internal data provider - if parameters.data is not None : - parameters.data.generateAttribute(self) - if parameters.dynamicTopology : - if parameters.elementType is not None : - addDynamicTopology(self, container = dataclasses.asdict(parameters.data)) + if self.parameters.data is not None : + self.parameters.data.generateAttribute(self) + if self.parameters.dynamicTopology : + if self.parameters.elementType is not None : + addDynamicTopology(self, container = dataclasses.asdict(self.parameters.data)) else: raise ValueError else: addStaticTopology(self, container = { - "position": parameters.data.position, - "edges": parameters.data.edges, - "triangles": parameters.data.triangles, - "quads": parameters.data.quads, - "tetrahedra": parameters.data.tetrahedra, - "hexahedra": parameters.data.hexahedra - }) + "position": self.parameters.data.position, + "edges": self.parameters.data.edges, + "triangles": self.parameters.data.triangles, + "quads": self.parameters.data.quads, + "tetrahedra": self.parameters.data.tetrahedra, + "hexahedra": self.parameters.data.hexahedra + }) \ No newline at end of file diff --git a/stlib/prefabs/collision.py b/stlib/prefabs/collision.py index da603146f..aba95d68b 100644 --- a/stlib/prefabs/collision.py +++ b/stlib/prefabs/collision.py @@ -25,15 +25,17 @@ class Collision(BasePrefab): def __init__(self, parameters: CollisionParameters): BasePrefab.__init__(self, parameters) - geom = self.add(Geometry, parameters.geometry) + def init(self): + + geom = self.add(Geometry, self.parameters.geometry) - self.addObject("MechanicalObject", template="Vec3", position=f"@{parameters.geometry.name}/container.position") - for primitive in parameters.primitives: + self.addObject("MechanicalObject", template="Vec3", position=f"@{self.parameters.geometry.name}/container.position") + for primitive in self.parameters.primitives: addCollisionModels(self, primitive, - topology=f"@{parameters.geometry.name}/container", - selfCollision=parameters.selfCollision, - group=parameters.group, - **parameters.kwargs) + topology=f"@{self.parameters.geometry.name}/container", + selfCollision=self.parameters.selfCollision, + group=self.parameters.group, + **self.parameters.kwargs) @staticmethod diff --git a/stlib/prefabs/material.py b/stlib/prefabs/material.py index 07009f421..070c7ac55 100644 --- a/stlib/prefabs/material.py +++ b/stlib/prefabs/material.py @@ -24,7 +24,8 @@ class Material(BasePrefab): def __init__(self, parameters: MaterialParameters): BasePrefab.__init__(self, parameters) - self.parameters = parameters + - self.addObject("MechanicalObject", name="States", template=str(parameters.stateType)) - parameters.addMaterial(self) + def init(self): + self.addObject("MechanicalObject", name="States", template=str(self.parameters.stateType)) + self.parameters.addMaterial(self) diff --git a/stlib/prefabs/visual.py b/stlib/prefabs/visual.py index bbc34bee1..2ced99a7f 100644 --- a/stlib/prefabs/visual.py +++ b/stlib/prefabs/visual.py @@ -20,8 +20,10 @@ class Visual(BasePrefab): def __init__(self, parameters: VisualParameters): BasePrefab.__init__(self, parameters) - self.geometry = self.add(Geometry, parameters.geometry) - self.addObject("OglModel", color=parameters.color, src=self.geometry.container.linkpath) + def init(self): + self.geometry = self.add(Geometry, self.parameters.geometry) + self.addObject("OglModel", color=self.parameters.color, src=self.geometry.container.linkpath) + @staticmethod def getParameters(**kwargs) -> VisualParameters: From aad2a5e4c4d3f54ff412d06d55f8b3f0a49a0be5 Mon Sep 17 00:00:00 2001 From: Damien Marchal Date: Thu, 17 Jul 2025 09:30:28 +0200 Subject: [PATCH 37/41] [STLIB] Add the unified "add" in Sofa.Core.Node (#503) * Add Sofa.Core.Node.add with preliminary test * rename the "add" function and make it private * Apply suggestions from code review Co-authored-by: Hugo --------- Co-authored-by: Hugo --- stlib/__init__.py | 55 ++++++++++++++++++++++++++++++++++ stlib/core/basePrefab.py | 27 ----------------- tests/test_new_add.py | 64 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 tests/test_new_add.py diff --git a/stlib/__init__.py b/stlib/__init__.py index 70108a419..01c21a403 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -1 +1,56 @@ __all__ = ["core","entities","prefabs","shapes"] + +import Sofa.Core +def __genericAdd(self : Sofa.Core.Node, typeName, **kwargs): + def findName(cname, names): + """Compute a working unique name in the node""" + rname = cname + for i in range(0, len(names)): + if rname not in names: + return rname + rname = cname + str(i+1) + return rname + + # Check if a name is provided, if not, use the one of the class + params = kwargs.copy() + isNode = False + if "name" not in params: + if isinstance(typeName, str): + params["name"] = typeName + isNode=True + elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Node): + params["name"] = "Node" + isNode=True + elif isinstance(typeName, Sofa.Core.Node): + params["name"] = "Node" + isNode=True + elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Object): + params["name"] = typeName.name.value + elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.ObjectDeclaration): + params["name"] = typeName.__name__ + else: + raise RuntimeError("Invalid argument ", typeName) + + # Check if the name already exists, if this happens, create a new one. + if params["name"] in self.children or params["name"] in self.objects: + names = {node.name.value for node in self.children} + names = names.union({object.name.value for object in self.objects}) + params["name"] = findName(params["name"], names) + + # Dispatch the creation to either addObject or addChild + if isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Node): + pref = self.addChild(typeName(params["name"])) + elif isinstance(typeName, Sofa.Core.Node): + pref = self.addChild(typeName) + elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Object): + pref = self.addObject(typeName(**params)) + elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.ObjectDeclaration): + pref = self.addObject(typeName.__name__, **params) + elif isinstance(typeName, str): + pref = self.addObject(typeName, **params) + else: + raise RuntimeError("Invalid argument", typeName) + return pref + +# Inject the method so it become available as if it was part of Sofa.Core.Node +Sofa.Core.Node.add = __genericAdd diff --git a/stlib/core/basePrefab.py b/stlib/core/basePrefab.py index c2e101483..1dd5665a3 100644 --- a/stlib/core/basePrefab.py +++ b/stlib/core/basePrefab.py @@ -3,33 +3,6 @@ import Sofa.Core from stlib.core.basePrefabParameters import BasePrefabParameters - -def addFromTypeName(self : Sofa.Core.Node, typeName, params = BasePrefabParameters, **kwargs): - def findName(cname, node): - """Compute a working unique name in the node""" - rname = cname - for i in range(0, len(node.children)): - if rname not in node.children: - return rname - rname = cname + str(i+1) - return rname - - for k,v in kwargs.items(): - if hasattr(params, k): - setattr(params, k, v) - - params = copy.copy(params) - if params.name in self.children: - params.name = findName(params.name, self) - - pref = self.addChild(typeName(params)) - pref.init() - - return pref - -Sofa.Core.Node.add = addFromTypeName - - class BasePrefab(Sofa.Core.Node): """ A Prefab is a Sofa.Node that assembles a set of components and nodes diff --git a/tests/test_new_add.py b/tests/test_new_add.py new file mode 100644 index 000000000..fcadda6f1 --- /dev/null +++ b/tests/test_new_add.py @@ -0,0 +1,64 @@ +import unittest +import Sofa +import SofaRuntime +import Sofa.Core +import stlib + +class ObjectDeclaration(object): + ... + +Sofa.Core.ObjectDeclaration = ObjectDeclaration + +class MechanicalObject(ObjectDeclaration): + pass + +class TestNewAdd(unittest.TestCase): + def test_add_node_with_node_type(self): + root = Sofa.Core.Node("root") + root.add(Sofa.Core.Node, name="aNodeA") + self.assertEqual(len(root.children), 1) + self.assertEqual(root.children[0].name.value, "aNodeA") + + def test_add_node_with_node_instance(self): + root = Sofa.Core.Node("root") + root.add(Sofa.Core.Node("aNodeB")) + self.assertEqual(len(root.children), 1) + self.assertEqual(root.children[0].name.value, "aNodeB") + + def test_add_object_with_string_type(self): + root = Sofa.Core.Node("root") + root.add("MechanicalObject", name="anObject1", position=[[1,2,3]]) + self.assertEqual(len(root.objects), 1) + self.assertEqual(root.objects[0].name.value, "anObject1") + self.assertEqual(root.objects[0].position.value.shape, (1,3)) + + def test_add_object_with_object_type(self): + root = Sofa.Core.Node("root") + root.add(MechanicalObject, name="anObject2", position=[[1,2,3]]) + self.assertEqual(len(root.objects), 1) + self.assertEqual(root.objects[0].name.value, "anObject2") + self.assertEqual(root.objects[0].position.value.shape, (1,3)) + + def test_automatic_name_generation(self): + root = Sofa.Core.Node("root") + root.add(MechanicalObject, position=[[1,2,3]]) + root.add(MechanicalObject, position=[[1,2,3]]) + root.add(MechanicalObject, position=[[1,2,3]]) + self.assertEqual(root.objects[0].name.value, "MechanicalObject") + self.assertEqual(root.objects[1].name.value, "MechanicalObject1") + self.assertEqual(root.objects[2].name.value, "MechanicalObject2") + + root.add(Sofa.Core.Node, name="TestNode") + root.add(Sofa.Core.Node, name="TestNode") + self.assertEqual(root.children[0].name.value, "TestNode") + self.assertEqual(root.children[1].name.value, "TestNode1") + + root.add(Sofa.Core.Node) + root.add(Sofa.Core.Node) + self.assertEqual(root.children[2].name.value, "Node") + self.assertEqual(root.children[3].name.value, "Node1") + +if __name__ == '__main__': + + SofaRuntime.importPlugin("Sofa.Component.StateContainer") + unittest.main() From 1b15154879a1839135b672d49855a7a9b27d7cac Mon Sep 17 00:00:00 2001 From: Paul Baksic Date: Thu, 17 Jul 2025 10:32:45 +0200 Subject: [PATCH 38/41] Add new tests for prefabs --- stlib/__init__.py | 4 +++- tests/test_new_add.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/stlib/__init__.py b/stlib/__init__.py index 01c21a403..51d5cd849 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -39,7 +39,9 @@ def findName(cname, names): # Dispatch the creation to either addObject or addChild if isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Node): - pref = self.addChild(typeName(params["name"])) + name = params["name"] + params.pop("name") + pref = self.addChild(typeName(name,**params)) elif isinstance(typeName, Sofa.Core.Node): pref = self.addChild(typeName) elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Object): diff --git a/tests/test_new_add.py b/tests/test_new_add.py index fcadda6f1..0c1036b65 100644 --- a/tests/test_new_add.py +++ b/tests/test_new_add.py @@ -58,6 +58,26 @@ def test_automatic_name_generation(self): self.assertEqual(root.children[2].name.value, "Node") self.assertEqual(root.children[3].name.value, "Node1") + def test_add_node_with_kwargs(self): + root = Sofa.Core.Node("root") + root.add(Sofa.Core.Node, name="aNodeC", gravity=[1,2,3]) + self.assertEqual(root.children[0].gravity.value, [1,2,3]) + + def test_add_instanciated_prefab(self): + root = Sofa.Core.Node("root") + from stlib.entities import Entity, EntityParameters + + bunnyParameters = EntityParameters() + bunny = root.add(Entity(bunnyParameters)) + + def test_add_prefab_with_parameter_object(self): + root = Sofa.Core.Node("root") + from stlib.entities import Entity, EntityParameters + + bunnyParameters = EntityParameters() + bunny = root.add(Entity, bunnyParameters) + + if __name__ == '__main__': SofaRuntime.importPlugin("Sofa.Component.StateContainer") From e2fff56b9ad801dd3b7763a21bf5029039c39e51 Mon Sep 17 00:00:00 2001 From: Paul Baksic Date: Thu, 17 Jul 2025 10:33:49 +0200 Subject: [PATCH 39/41] Revert modification of __genericAdd --- stlib/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/stlib/__init__.py b/stlib/__init__.py index 51d5cd849..01c21a403 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -39,9 +39,7 @@ def findName(cname, names): # Dispatch the creation to either addObject or addChild if isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Node): - name = params["name"] - params.pop("name") - pref = self.addChild(typeName(name,**params)) + pref = self.addChild(typeName(params["name"])) elif isinstance(typeName, Sofa.Core.Node): pref = self.addChild(typeName) elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Object): From 9de87d48465322ade1d1d3065be33935d1f20833 Mon Sep 17 00:00:00 2001 From: hugtalbot Date: Thu, 17 Jul 2025 16:44:15 +0200 Subject: [PATCH 40/41] update addMass using elementType --- splib/mechanics/mass.py | 21 ++++++++++++--------- stlib/entities/deformable/__deformable__.py | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/splib/mechanics/mass.py b/splib/mechanics/mass.py index 917fc9365..cb3478564 100644 --- a/splib/mechanics/mass.py +++ b/splib/mechanics/mass.py @@ -3,18 +3,21 @@ from splib.core.enum_types import ElementType -# TODO : use the massDensity only and deduce totalMass if necessary from it + volume +# TODO : use the massDensity ONLY and deduce totalMass if necessary from it + volume + @ReusableMethod -def addMass(node, template, totalMass=DEFAULT_VALUE, massDensity=DEFAULT_VALUE, lumping=DEFAULT_VALUE, **kwargs): +def addMass(node, elem:ElementType, totalMass=DEFAULT_VALUE, massDensity=DEFAULT_VALUE, lumping=DEFAULT_VALUE, **kwargs): if (not isDefault(totalMass)) and (not isDefault(massDensity)) : print("[warning] You defined the totalMass and the massDensity in the same time, only taking massDensity into account") - kwargs.pop('massDensity') - - # if(template=="Rigid3"): - node.addObject("UniformMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) - # else: - # node.addObject("MeshMatrixMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) - + del kwargs["massDensity"] + if(elem !=ElementType.POINTS and elem !=ElementType.EDGES): + node.addObject("MeshMatrixMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, **kwargs) + else: + if (not isDefault(massDensity)) : + print("[warning] mass density can only be used on a surface or volumetric topology. Please use totalMass instead") + if (not isDefault(lumping)) : + print("[warning] lumping can only be set for surface or volumetric topology") + node.addObject("UniformMass",name="mass", totalMass=totalMass, **kwargs) diff --git a/stlib/entities/deformable/__deformable__.py b/stlib/entities/deformable/__deformable__.py index 458289c74..4cbdf1296 100644 --- a/stlib/entities/deformable/__deformable__.py +++ b/stlib/entities/deformable/__deformable__.py @@ -14,7 +14,7 @@ class DeformableBehaviorParameters(MaterialParameters): parameters : list[float] = dataclasses.field(default_factory=lambda: [1000, 0.45]) # young modulus, poisson ratio def __addDeformableMaterial(node): - addMass(node, node.parameters.stateType, massDensity=node.parameters.massDensity, lumping=node.parameters.massLumping) + addMass(node, node.parameters.elementType, massDensity=node.parameters.massDensity, lumping=node.parameters.massLumping) # TODO : change this with inheritance if(node.parameters.constitutiveLawType == ConstitutiveLaw.HYPERELASTIC): addHyperelasticity(node, node.parameters.elementType, node.parameters.parameters, topology="@../Geometry/container") From 6721f3f1a37a3f9936c356115578d9eda62197e7 Mon Sep 17 00:00:00 2001 From: Paul Baksic Date: Tue, 28 Oct 2025 14:16:53 +0100 Subject: [PATCH 41/41] Modify generic add to support object creation passing parameter for prefab --- stlib/__init__.py | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/stlib/__init__.py b/stlib/__init__.py index 01c21a403..6c3c338bc 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -1,7 +1,14 @@ __all__ = ["core","entities","prefabs","shapes"] import Sofa.Core -def __genericAdd(self : Sofa.Core.Node, typeName, **kwargs): + +from stlib.core.baseParameters import BaseParameters +from stlib.core.basePrefab import BasePrefab + + +def __genericAdd(self : Sofa.Core.Node, typeName, *param, **kwargs): + + def findName(cname, names): """Compute a working unique name in the node""" rname = cname @@ -11,7 +18,37 @@ def findName(cname, names): rname = cname + str(i+1) return rname - # Check if a name is provided, if not, use the one of the class + def checkName(context : Sofa.Core.Node, name): + # Check if the name already exists, if this happens, create a new one. + if name in context.children or name in context.objects: + names = {node.name.value for node in context.children} + names = names.union({object.name.value for object in context.objects}) + name = findName(name, names) + return name + + + if len(param) == 1 and isinstance(typeName, type) and not issubclass(typeName, BasePrefab): + raise RuntimeError("Invalid argument : only prefabs take positionnal argument which should be a parameter") + elif len(param) > 1: + raise RuntimeError("Invalid argument : only one positionnal argument accepted and only when used with a prefab") + elif len(param) == 0 and isinstance(typeName, type) and issubclass(typeName, BasePrefab): + raise RuntimeError("Invalid argument : one positionnal argument is required when calling add with prefab type") + elif len(param) == 1 and isinstance(typeName, type) and issubclass(typeName, BasePrefab) and not isinstance(param[0], BaseParameters): + raise RuntimeError("Invalid argument : when calling add with prefab type the positionnal argument is expected to be a type derived from stlib.core.BaseParameter") + + + if len(param) == 1 and isinstance(typeName, type) and issubclass(typeName, BasePrefab): + param[0].name = checkName(self, param[0].name) + if(len(kwargs)): + param[0].kwargs = kwargs.copy() + + newEntity = self.addChild(typeName(param[0])) + newEntity.init() + return newEntity + + ## If we ever get here, it means we are not adding a prefab by giving its type name and its parameter set + + # Check if a name is provided, if not, use the one of the class params = kwargs.copy() isNode = False if "name" not in params: @@ -31,11 +68,7 @@ def findName(cname, names): else: raise RuntimeError("Invalid argument ", typeName) - # Check if the name already exists, if this happens, create a new one. - if params["name"] in self.children or params["name"] in self.objects: - names = {node.name.value for node in self.children} - names = names.union({object.name.value for object in self.objects}) - params["name"] = findName(params["name"], names) + params["name"] = checkName(self, params["name"]) # Dispatch the creation to either addObject or addChild if isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Node):