diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Types/Binding_CompressedRowSparseMatrix.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Types/Binding_CompressedRowSparseMatrix.cpp index 62b337fd4..ca2e77099 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Types/Binding_CompressedRowSparseMatrix.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Types/Binding_CompressedRowSparseMatrix.cpp @@ -53,10 +53,18 @@ void bindCompressedRowSparseMatrixConstraint(pybind11::module& m) crsmc.def_property_readonly("value", [](sofa::Data& self) { - sofa::helper::ReadAccessor accessor(self); + sofa::helper::WriteAccessor accessor(self); + accessor.wref().compress(); return toEigen(accessor.ref()); }); + crsmc.def("add", [](sofa::Data& self, sofa::Index row, sofa::Index col, const typename MatrixDeriv::Block value) + { + sofa::helper::WriteAccessor accessor(self); + auto line = accessor->writeLine(row); + line.addCol(col, value); + }); + PythonFactory::registerType(MatrixDeriv::Name(), [](sofa::core::BaseData* data) -> py::object { auto matrix = reinterpret_cast*>(data); return py::cast(matrix); diff --git a/examples/stlib/SofaScene.py b/examples/stlib/SofaScene.py index 4e60200c6..84b05451e 100644 --- a/examples/stlib/SofaScene.py +++ b/examples/stlib/SofaScene.py @@ -3,7 +3,7 @@ from stlib.geometries.plane import PlaneParameters from stlib.geometries.file import FileParameters from stlib.geometries.extract import ExtractParameters -from stlib.materials.deformable import DeformableBehaviorParameters +from stlib.materials.deformable import DeformableMaterialParameters from stlib.collision import Collision, CollisionParameters from stlib.entities import Entity, EntityParameters from stlib.visual import Visual, VisualParameters @@ -66,7 +66,7 @@ def createScene(root): LogoParams = EntityParameters(name = "Logo", geometry = FileParameters(filename="mesh/SofaScene/Logo.vtk"), - material = DeformableBehaviorParameters(), + material = DeformableMaterialParameters(), collision = CollisionParameters(geometry = FileParameters(filename="mesh/SofaScene/LogoColli.sph")), visual = VisualParameters(geometry = FileParameters(filename="mesh/SofaScene/LogoVisu.obj"))) @@ -94,12 +94,12 @@ def createScene(root): SParams.name = "S" SParams.geometry = FileParameters(filename="mesh/SofaScene/S.vtk") SParams.geometry.elementType = ElementType.TETRAHEDRA - SParams.material = DeformableBehaviorParameters() + SParams.material = DeformableMaterialParameters() SParams.material.constitutiveLawType = ConstitutiveLaw.ELASTIC SParams.material.parameters = [200, 0.45] def SAddMaterial(node): - DeformableBehaviorParameters.addDeformableMaterial(node) + DeformableMaterialParameters.addDeformableMaterial(node) #TODO deal with that is a more smooth way in the material directly node.addObject("LinearSolverConstraintCorrection", name="ConstraintCorrection", linearSolver=SNode.LinearSolver.linkpath, ODESolver=SNode.ODESolver.linkpath) diff --git a/splib/core/utils.py b/splib/core/utils.py index 56b91f53c..c34b28830 100644 --- a/splib/core/utils.py +++ b/splib/core/utils.py @@ -1,6 +1,7 @@ from typing import List, Callable, Tuple, Dict from functools import wraps + class defaultValueType(): def __init__(self): pass @@ -32,5 +33,24 @@ def wrapper(*args, **kwargs): return MapArg +REQUIRES_COLLISIONPIPELINE = "requiresCollisionPipeline" + +def setRequiresCollisionPipeline(rootnode): + if rootnode is not None: + if rootnode.findData(REQUIRES_COLLISIONPIPELINE) is None: + rootnode.addData(name=REQUIRES_COLLISIONPIPELINE, type="bool", value=True, help="Some prefabs in the scene requires a collision pipeline.") + else: + rootnode.requiresCollisionPipeline.value = True + + +REQUIRES_LAGRANGIANCONSTRAINTSOLVER = "requiresLagrangianConstraintSolver" + +def setRequiresLagrangianConstraintSolver(rootnode): + if rootnode is not None: + if rootnode.findData(REQUIRES_LAGRANGIANCONSTRAINTSOLVER) is None: + rootnode.addData(name=REQUIRES_LAGRANGIANCONSTRAINTSOLVER, type="bool", value=True, help="Some prefabs in the scene requires a Lagrangian constraint solver.") + else: + rootnode.requiresLagrangianConstraintSolver.value = True + diff --git a/splib/mechanics/mass.py b/splib/mechanics/mass.py index c003c3e64..37b6d9db0 100644 --- a/splib/mechanics/mass.py +++ b/splib/mechanics/mass.py @@ -3,21 +3,24 @@ from splib.core.enum_types import ElementType -# TODO : use the massDensity ONLY and deduce totalMass if necessary from it + volume - @ReusableMethod -def addMass(node, elem:ElementType, totalMass=DEFAULT_VALUE, massDensity=DEFAULT_VALUE, lumping=DEFAULT_VALUE, topology=DEFAULT_VALUE, **kwargs): +def addMass(node, elementType:ElementType, totalMass=DEFAULT_VALUE, massDensity=DEFAULT_VALUE, lumping=DEFAULT_VALUE, topology=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") del kwargs["massDensity"] - if(elem !=ElementType.POINTS and elem !=ElementType.EDGES): - node.addObject("MeshMatrixMass",name="mass", totalMass=totalMass, massDensity=massDensity, lumping=lumping, topology=topology, **kwargs) + if(elementType is not None and elementType !=ElementType.POINTS and elementType !=ElementType.EDGES): + node.addObject("MeshMatrixMass", + name="mass", + totalMass=totalMass, + massDensity=massDensity, + lumping=lumping, + topology=topology, **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, topology=topology,**kwargs) + node.addObject("UniformMass", name="mass", totalMass=totalMass, topology=topology,**kwargs) diff --git a/stlib/__init__.py b/stlib/__init__.py index 0ad659858..775d7990b 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -1,4 +1,4 @@ -__all__ = ["core","entities","geometries","materials","collision","visual"] +__all__ = ["core","entities","geometries","materials","collision","visual","prefabs"] import Sofa.Core from stlib.core.basePrefab import BasePrefab @@ -48,6 +48,7 @@ def checkName(context : Sofa.Core.Node, name): else: params["name"] = checkName(self, params["name"]) + # Dispatch the creation to either addObject or addChild if isinstance(typeName, type) and issubclass(typeName, BasePrefab): pref = self.addChild(typeName(**params)) diff --git a/stlib/collision.py b/stlib/collision.py index b8f059249..f43e9a95c 100644 --- a/stlib/collision.py +++ b/stlib/collision.py @@ -1,27 +1,28 @@ from stlib.core.basePrefab import BasePrefab -from stlib.core.baseParameters import BaseParameters, Optional, dataclasses +from stlib.core.baseParameters import BaseParameters, Optional from stlib.geometries import Geometry, GeometryParameters from stlib.geometries.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 +from splib.core.utils import setRequiresCollisionPipeline + -@dataclasses.dataclass class CollisionParameters(BaseParameters): name : str = "Collision" - primitives : list[CollisionPrimitive] = dataclasses.field(default_factory = lambda :[CollisionPrimitive.TRIANGLES]) + primitives : list[CollisionPrimitive] = [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()) + geometry : GeometryParameters = GeometryParameters() class Collision(BasePrefab): + def __init__(self, parameters: CollisionParameters): BasePrefab.__init__(self, parameters) @@ -29,6 +30,8 @@ def init(self): geom = self.add(Geometry, parameters = self.parameters.geometry) + setRequiresCollisionPipeline(rootnode=self.getRoot()) + self.addObject("MechanicalObject", template="Vec3", position=f"@{self.parameters.geometry.name}/container.position") for primitive in self.parameters.primitives: addCollisionModels(self, primitive, diff --git a/stlib/core/baseParameters.py b/stlib/core/baseParameters.py index be7a633b8..77c73cece 100644 --- a/stlib/core/baseParameters.py +++ b/stlib/core/baseParameters.py @@ -1,13 +1,34 @@ -import dataclasses from splib.core.utils import DEFAULT_VALUE -import dataclasses +from pydantic import BaseModel, ValidationError +from dynapydantic import SubclassTrackingModel from typing import Callable, Optional, Any +import Sofa + +class BaseParameters(SubclassTrackingModel, + discriminator_field="type", + discriminator_value_generator=lambda t: t.__name__,): -@dataclasses.dataclass -class BaseParameters(object): name : str = "Object" - kwargs : dict = dataclasses.field(default_factory=dict) + kwargs : dict = {} + + @classmethod + def fromYaml(self, data: str): + import yaml + dataDict = yaml.safe_load(data) + return self.fromDict(dataDict) + + @classmethod + def fromDict(self, data: dict): + try: + return self.model_validate(data, strict=True) + except ValidationError as exc: + for error in exc.errors(): + loc = error.get("loc") + message = "" + for locPart in loc: + message += locPart.__str__() + ": " + message += error.get("msg") + Sofa.msg_error(self.__name__, message) + - def toDict(self): - return dataclasses.asdict(self) diff --git a/stlib/core/basePrefabParameters.py b/stlib/core/basePrefabParameters.py index d6a69dee9..f667d3c96 100644 --- a/stlib/core/basePrefabParameters.py +++ b/stlib/core/basePrefabParameters.py @@ -1,15 +1,12 @@ -import dataclasses +from stlib.core.baseParameters import BaseParameters -@dataclasses.dataclass -class BasePrefabParameters(object): +class BasePrefabParameters(BaseParameters): name : str = "object" - kwargs : dict = dataclasses.field(default_factory=dict) + kwargs : dict = {} # Transformation information # TODO: these data are going to be added in Node in SOFA (C++ implementation) - 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) + translation : list[float] = [0., 0., 0.] + rotation : list[float] = [0., 0., 0.] + scale : list[float] = [1., 1., 1.] + \ No newline at end of file diff --git a/stlib/entities/__entity__.py b/stlib/entities/__entity__.py index bc7c76711..77ed051ec 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -3,27 +3,30 @@ from stlib.visual import VisualParameters, Visual from stlib.materials import Material, MaterialParameters from stlib.geometries import Geometry -import dataclasses from typing import Callable, Optional -from stlib.geometries import GeometryParameters +from stlib.geometries import GeometryParameters, InternalDataProvider from splib.core.enum_types import StateType from stlib.core.basePrefab import BasePrefab +from stlib.geometries.file import FileParameters +from stlib.materials.rigid import RigidMaterialParameters +from splib.core.enum_types import ElementType + +from dynapydantic import Polymorphic +import Sofa + -@dataclasses.dataclass class EntityParameters(BaseParameters): name : str = "Entity" - stateType : StateType = StateType.VEC3 - ### QUID - addCollision : Optional[Callable] = lambda x : Collision(CollisionParameters()) - addVisual : Optional[Callable] = lambda x : Visual(VisualParameters()) + addCollision : Optional[Callable] = Collision(CollisionParameters()) + addVisual : Optional[Callable] = Visual(VisualParameters()) - geometry : GeometryParameters = None - material : MaterialParameters = None + geometry : Polymorphic[GeometryParameters] = GeometryParameters(elementType = ElementType.POINTS, data = InternalDataProvider(position = [[0., 0., 0.]])) + material : Polymorphic[MaterialParameters] = RigidMaterialParameters() collision : Optional[CollisionParameters] = None - visual : Optional[VisualParameters] = None + visual : Optional[VisualParameters] = VisualParameters(geometry = FileParameters(filename="mesh/cube.obj")) @@ -38,21 +41,16 @@ class Entity(BasePrefab): parameters : EntityParameters - def __init__(self, parameters=EntityParameters(), **kwargs): + def __init__(self, parameters: EntityParameters): BasePrefab.__init__(self, parameters) def init(self): self.geometry = self.add(Geometry, parameters=self.parameters.geometry) - ### Check compatilibility of Material - 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.stateType - self.material = self.add(Material, parameters=self.parameters.material) - self.material.States.position.parent = self.geometry.container.position.linkpath - + self.material.getMechanicalState().topology = self.geometry.container.linkpath + if self.parameters.collision is not None: self.collision = self.add(Collision, parameters=self.parameters.collision) self.addMapping(self.collision) @@ -64,18 +62,18 @@ def init(self): def addMapping(self, destinationPrefab): - template = f'{self.parameters.stateType},Vec3' # TODO: check that it is always true + template = f'{self.parameters.material.stateType},Vec3' # TODO: check that it is always true #TODO: all paths are expecting Geometry to be called Geomtry and so on. We need to robustify this by using the name parameter somehow - if( self.parameters.stateType == StateType.VEC3): + if( self.parameters.material.stateType == StateType.VEC3): destinationPrefab.addObject("BarycentricMapping", output=destinationPrefab.linkpath, - output_topology=destinationPrefab.Geometry.container.linkpath, - input=self.Material.linkpath, - input_topology=self.Geometry.container.linkpath, + output_topology=destinationPrefab.geometry.container.linkpath, + input=self.material.linkpath, + input_topology=self.geometry.container.linkpath, template=template) else: destinationPrefab.addObject("RigidMapping", output=destinationPrefab.linkpath, - input=self.Material.linkpath, + input=self.material.linkpath, template=template) diff --git a/stlib/geometries/__geometry__.py b/stlib/geometries/__geometry__.py index 57d0e5c76..034a0bfd2 100644 --- a/stlib/geometries/__geometry__.py +++ b/stlib/geometries/__geometry__.py @@ -1,5 +1,5 @@ from stlib.core.basePrefab import BasePrefab -from stlib.core.baseParameters import BaseParameters, Optional, dataclasses, Any +from stlib.core.baseParameters import BaseParameters, Optional, Any from splib.topology.dynamic import addDynamicTopology from splib.topology.static import addStaticTopology from splib.core.enum_types import ElementType @@ -11,8 +11,7 @@ class Geometry(BasePrefab):... -@dataclasses.dataclass -class InternalDataProvider(object): +class InternalDataProvider(BaseParameters): position : Any = None # Topology information edges : Any = DEFAULT_VALUE @@ -21,35 +20,26 @@ class InternalDataProvider(object): tetrahedra : Any = DEFAULT_VALUE hexahedra : Any = DEFAULT_VALUE + @classmethod def generateAttribute(self, parent : Geometry): pass -@dataclasses.dataclass class GeometryParameters(BaseParameters): name : str = "Geometry" - # Type of the highest degree element - elementType : Optional[ElementType] = None + elementType : Optional[ElementType] = None # Type of the highest degree element data : Optional[InternalDataProvider] = None dynamicTopology : bool = False - def Data(self): - return InternalDataProvider() - - class Geometry(BasePrefab): - # 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) - - def init(self): @@ -59,14 +49,16 @@ def init(self): if self.parameters.dynamicTopology : if self.parameters.elementType is not None : - addDynamicTopology(self, elementType=self.parameters.elementType, container = { - "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 - }) + addDynamicTopology(self, + elementType=self.parameters.elementType, + container = { + "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 + }) else: raise ValueError else: diff --git a/stlib/geometries/cube.py b/stlib/geometries/cube.py index 4d5a52ca3..78f1c2658 100644 --- a/stlib/geometries/cube.py +++ b/stlib/geometries/cube.py @@ -1,14 +1,6 @@ -from stlib.geometries import GeometryParameters +from stlib.geometries import FileParameters -class CubeParameters(GeometryParameters): - def __init__(self, center, edgeLength, pointPerEdge, dynamicTopology = False): +class CubeParameters(FileParameters): - 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 + filename : str = "mesh/cube.obj" \ No newline at end of file diff --git a/stlib/geometries/extract.py b/stlib/geometries/extract.py index 5ee1b3762..d640e0938 100644 --- a/stlib/geometries/extract.py +++ b/stlib/geometries/extract.py @@ -1,5 +1,4 @@ from stlib.geometries 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 @@ -13,18 +12,11 @@ class ExtractInternalDataProvider(InternalDataProvider): sourceType : ElementType sourceName : str - def __init__(self, destinationType : ElementType, sourceType : ElementType, sourceName : str): - self.destinationType = destinationType - self.sourceType = sourceType - self.sourceName = sourceName - - def __post_init__(self): + def model_post_init(self, __context): 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): node = parent.addChild("ExtractedGeometry") diff --git a/stlib/geometries/file.py b/stlib/geometries/file.py index 601782f9d..dbf180885 100644 --- a/stlib/geometries/file.py +++ b/stlib/geometries/file.py @@ -1,16 +1,11 @@ from stlib.geometries 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 -@dataclasses.dataclass class FileInternalDataProvider(InternalDataProvider): - filename : str = "mesh/cube.obj" - - def __post_init__(self, **kwargs): - InternalDataProvider.__init__(self,**kwargs) + filename : str = "mesh/cube.obj" # This should be linked to FileParameters.filename def generateAttribute(self, parent : Geometry): loadMesh(parent, self.filename) @@ -32,9 +27,11 @@ def generateAttribute(self, parent : Geometry): class FileParameters(GeometryParameters): - def __init__(self, filename, dynamicTopology = False, elementType : ElementType = None ): - GeometryParameters.__init__(self, - data = FileInternalDataProvider(filename=filename), - dynamicTopology = dynamicTopology, - elementType = elementType) + filename : str = "mesh/cube.obj" + dynamicTopology : bool = False + elementType : ElementType = None + + def model_post_init(self, __context): + self.data = FileInternalDataProvider(filename=self.filename) + diff --git a/stlib/geometries/plane.py b/stlib/geometries/plane.py index 2c3c637a4..1a35cace8 100644 --- a/stlib/geometries/plane.py +++ b/stlib/geometries/plane.py @@ -1,20 +1,15 @@ from stlib.geometries import GeometryParameters, InternalDataProvider, Geometry -import dataclasses import numpy as np -@dataclasses.dataclass class PlaneDataProvider(InternalDataProvider): - center : np.ndarray[float] = dataclasses.field(default_factory = lambda : np.array([0,0,0])) - normal : np.ndarray[float] = dataclasses.field(default_factory = lambda : np.array([0,0,1])) - lengthNormal : np.ndarray[float] = dataclasses.field(default_factory = lambda : np.array([1,0,0])) + center : np.ndarray[float] = np.array([0,0,0]) + normal : np.ndarray[float] = np.array([0,0,1]) + lengthNormal : np.ndarray[float] = np.array([1,0,0]) lengthNbEdge : int = 1 widthNbEdge : int = 1 lengthSize : float = 1.0 widthSize : float = 1.0 - def __post_init__(self, **kwargs): - InternalDataProvider.__init__(self,**kwargs) - def generateAttribute(self, parent : Geometry): lengthEdgeSize = self.lengthSize / self.lengthNbEdge @@ -37,9 +32,22 @@ def generateAttribute(self, parent : Geometry): - class PlaneParameters(GeometryParameters): - def __init__(self, center, normal, lengthNormal, lengthNbEdge, widthNbEdge, lengthSize, widthSize, dynamicTopology = False): - GeometryParameters.__init__(self, data = PlaneDataProvider(center=center, normal=normal, lengthNormal=lengthNormal, lengthNbEdge=lengthNbEdge, widthNbEdge=widthNbEdge, lengthSize=lengthSize, widthSize=widthSize), - dynamicTopology = dynamicTopology) + center: list[float, float, float] = [0, 0, 0] + normal: list[float, float, float] = [0, 0, 1] + lengthNormal: list[float, float, float] = [1, 0, 0] + lengthNbEdge: int = 1 + widthNbEdge: int = 1 + lengthSize: float = 1.0 + widthSize: float = 1.0 + dynamicTopology: bool = False + + def model_post_init(self, __context): + self.data = PlaneDataProvider(center=self.center, + normal=self.normal, + lengthNormal=self.lengthNormal, + lengthNbEdge=self.lengthNbEdge, + widthNbEdge=self.widthNbEdge, + lengthSize=self.lengthSize, + widthSize=self.widthSize) diff --git a/stlib/materials/__material__.py b/stlib/materials/__material__.py index ff88dbd43..09a59d1c2 100644 --- a/stlib/materials/__material__.py +++ b/stlib/materials/__material__.py @@ -1,23 +1,27 @@ -from stlib.core.baseParameters import BaseParameters, Callable, Optional, dataclasses, Any -from splib.core.utils import defaultValueType, DEFAULT_VALUE, isDefault +from stlib.core.baseParameters import BaseParameters, Callable, Optional, Any +from splib.core.utils import DEFAULT_VALUE 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" + position : Optional[list[float]] = None + massDensity : float = DEFAULT_VALUE massLumping : bool = DEFAULT_VALUE stateType : StateType = StateType.VEC3 - addMaterial : Optional[Callable] = lambda node : addMass(node, node.parameters.stateType, massDensity=node.parameters.massDensity, lumping=node.parameters.massLumping) + addMaterial : Optional[Callable] = lambda node : addMass(node, + elementType=None, + massDensity=node.parameters.massDensity, + lumping=node.parameters.massLumping) -# TODO : previously called Behavior class Material(BasePrefab): parameters : MaterialParameters @@ -25,7 +29,7 @@ class Material(BasePrefab): def __init__(self, parameters: MaterialParameters): BasePrefab.__init__(self, parameters) - def init(self): - self.addObject("MechanicalObject", name="States", template=str(self.parameters.stateType)) + self.addObject("MechanicalObject", name="States", template=str(self.parameters.stateType), + position = self.parameters.position if self.parameters.position is not None else "") self.parameters.addMaterial(self) diff --git a/stlib/materials/deformable.py b/stlib/materials/deformable.py index a24763df5..d1e4e0524 100644 --- a/stlib/materials/deformable.py +++ b/stlib/materials/deformable.py @@ -1,17 +1,16 @@ from stlib.materials import MaterialParameters from splib.core.enum_types import ConstitutiveLaw -from stlib.core.baseParameters import Callable, Optional, dataclasses +from stlib.core.baseParameters import Callable, Optional from splib.mechanics.linear_elasticity import * from splib.mechanics.hyperelasticity import * from splib.mechanics.mass import addMass -@dataclasses.dataclass -class DeformableBehaviorParameters(MaterialParameters): +class DeformableMaterialParameters(MaterialParameters): constitutiveLawType : ConstitutiveLaw = ConstitutiveLaw.ELASTIC elementType : ElementType = ElementType.TETRAHEDRA - parameters : list[float] = dataclasses.field(default_factory=lambda: [1000, 0.45]) # young modulus, poisson ratio + parameters : list[float] = [1000, 0.45] # young modulus, poisson ratio def __addDeformableMaterial(node): @@ -28,25 +27,3 @@ def __addDeformableMaterial(node): addMaterial : Optional[Callable] = __addDeformableMaterial - -def createScene(root) : - from stlib.entities import Entity, EntityParameters - from stlib.visual import VisualParameters - from stlib.geometries.file import FileParameters - - root.addObject('RequiredPlugin', name='Sofa.Component.Visual') # Needed to use components [VisualStyle] - 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, parameters=bunnyParameters) - # bunny.init() \ No newline at end of file diff --git a/stlib/materials/rigid.py b/stlib/materials/rigid.py index 5668b3562..20a7dc3ab 100644 --- a/stlib/materials/rigid.py +++ b/stlib/materials/rigid.py @@ -1,13 +1,9 @@ -from stlib.core.baseParameters import BaseParameters, Optional, dataclasses -from stlib.geometries import GeometryParameters +from stlib.materials import MaterialParameters +from splib.core.enum_types import StateType +class RigidMaterialParameters(MaterialParameters): -@dataclasses.dataclass -class RigidParameters(BaseParameters): + stateType : StateType = StateType.RIGID - geometry : GeometryParameters - mass : Optional[float] = None - - def toDict(self): - return dataclasses.asdict(self) + position : list[list[float]] = [[0., 0., 0., 0., 0., 0., 1.]] diff --git a/stlib/prefabs/__init__.py b/stlib/prefabs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/prefabs/bunny.py b/stlib/prefabs/bunny.py new file mode 100644 index 000000000..f1febde44 --- /dev/null +++ b/stlib/prefabs/bunny.py @@ -0,0 +1,27 @@ +from stlib.entities import Entity, EntityParameters +from stlib.visual import VisualParameters +from stlib.geometries.file import FileParameters +from stlib.materials.deformable import DeformableMaterialParameters +from splib.core.enum_types import ElementType + +from typing import Optional + + +class BunnyParameters(EntityParameters): + name : str = "Bunny" + deformable : bool = False + visual : Optional[VisualParameters] = VisualParameters(geometry = FileParameters(filename="mesh/Bunny.stl")) + + def model_post_init(self, _): + # TODO: + # 1. apply size as scale in geometry, material, collision and visual + if self.deformable: + self.geometry = FileParameters(filename="mesh/Bunny.vtk", elementType=ElementType.TETRAHEDRA) + self.material = DeformableMaterialParameters() + return + + +class Bunny(Entity): + + def __init__(self, parameters: BunnyParameters): + super().__init__(parameters) \ No newline at end of file diff --git a/stlib/prefabs/cube.py b/stlib/prefabs/cube.py new file mode 100644 index 000000000..1164c7ef1 --- /dev/null +++ b/stlib/prefabs/cube.py @@ -0,0 +1,24 @@ +from stlib.entities import Entity, EntityParameters +from stlib.materials.deformable import DeformableMaterialParameters +from stlib.geometries.file import FileParameters +from splib.core.enum_types import ElementType + + +class CubeParameters(EntityParameters): + name : str = "Cube" + size : float = 1 + deformable : bool = False + + def model_post_init(self, _): + # TODO : + # 1. apply size as scale in geometry, material, collision and visual + if self.deformable: + self.geometry = FileParameters(filename="mesh/sphere.vtk", elementType=ElementType.TETRAHEDRA) + self.material = DeformableMaterialParameters() + return + + +class Cube(Entity): + + def __init__(self, parameters: CubeParameters): + super().__init__(parameters) \ No newline at end of file diff --git a/stlib/prefabs/sphere.py b/stlib/prefabs/sphere.py new file mode 100644 index 000000000..b46bf83dc --- /dev/null +++ b/stlib/prefabs/sphere.py @@ -0,0 +1,28 @@ +from stlib.entities import Entity, EntityParameters +from stlib.visual import VisualParameters +from stlib.geometries.file import FileParameters +from stlib.materials.deformable import DeformableMaterialParameters +from splib.core.enum_types import ElementType + +from typing import Optional + + +class SphereParameters(EntityParameters): + name : str = "Sphere" + radius : float = 1 + deformable : bool = False + visual : Optional[VisualParameters] = VisualParameters(geometry = FileParameters(filename="mesh/sphere.obj")) + + def model_post_init(self, _): + # TODO: + # 1. apply size as scale in geometry, material, collision and visual + if self.deformable: + self.geometry = FileParameters(filename="mesh/sphere.vtk", elementType=ElementType.TETRAHEDRA) + self.material = DeformableMaterialParameters() + return + + +class Sphere(Entity): + + def __init__(self, parameters: SphereParameters): + super().__init__(parameters) \ No newline at end of file diff --git a/stlib/settings/__init__.py b/stlib/settings/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stlib/settings/simulation.py b/stlib/settings/simulation.py new file mode 100644 index 000000000..c7ba40525 --- /dev/null +++ b/stlib/settings/simulation.py @@ -0,0 +1,40 @@ +import Sofa.Core +from stlib.core.basePrefab import BasePrefab +from stlib.core.baseParameters import BaseParameters +from splib.core.utils import REQUIRES_COLLISIONPIPELINE, REQUIRES_LAGRANGIANCONSTRAINTSOLVER + + +class SimulationSettingsParameters(BaseParameters): + name : str = "Simulation" + + +class SimulationSettings(BasePrefab): + + def __init__(self, parameters: SimulationSettingsParameters): + BasePrefab.__init__(self, parameters) + + def init(self): + rootnode = self.parents[0] + + rootnode.add('DefaultVisualManagerLoop') + rootnode.add('VisualStyle') + rootnode.add('InteractiveCamera') + + if rootnode.findData(REQUIRES_COLLISIONPIPELINE) and rootnode.findData(REQUIRES_COLLISIONPIPELINE).value: + rootnode.add('CollisionPipeline') + rootnode.add('RuleBasedContactManager', responseParams='mu=0', response='FrictionContactConstraint') + rootnode.add('ParallelBruteForceBroadPhase') + rootnode.add('ParallelBVHNarrowPhase') + rootnode.add('LocalMinDistance', alarmDistance=5, contactDistance=1) + + if rootnode.findData(REQUIRES_LAGRANGIANCONSTRAINTSOLVER) and rootnode.findData(REQUIRES_LAGRANGIANCONSTRAINTSOLVER).value: + rootnode.add('FreeMotionAnimationLoop') + rootnode.add('BlockGaussSeidelConstraintSolver') + else: + rootnode.add('DefaultAnimationLoop') + + self.add('EulerImplicitSolver') + self.add('SparseLDLSolver') + + if rootnode.findData(REQUIRES_LAGRANGIANCONSTRAINTSOLVER) and rootnode.findData(REQUIRES_LAGRANGIANCONSTRAINTSOLVER).value: + self.add('GenericConstraintCorrection') diff --git a/stlib/visual.py b/stlib/visual.py index a0f377f7d..5f960d07c 100644 --- a/stlib/visual.py +++ b/stlib/visual.py @@ -1,18 +1,18 @@ from stlib.core.basePrefab import BasePrefab -from stlib.core.baseParameters import BaseParameters, Optional, dataclasses +from stlib.core.baseParameters import BaseParameters, Optional from stlib.geometries import Geometry, GeometryParameters from stlib.geometries.file import FileParameters from splib.core.utils import DEFAULT_VALUE -from Sofa.Core import Object +from dynapydantic import Polymorphic + -@dataclasses.dataclass class VisualParameters(BaseParameters): name : str = "Visual" color : Optional[list[float]] = DEFAULT_VALUE texture : Optional[str] = DEFAULT_VALUE - geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) + geometry : Polymorphic[GeometryParameters] = None class Visual(BasePrefab): @@ -25,15 +25,10 @@ def init(self): self.addObject("OglModel", color=self.parameters.color, src=self.geometry.container.linkpath) - @staticmethod - def getParameters(**kwargs) -> VisualParameters: - return VisualParameters(**kwargs) - - def createScene(root): # Create a visual from a mesh file - parameters = Visual.getParameters() + parameters = VisualParameters() parameters.name = "LiverVisual" parameters.geometry = FileParameters(filename="mesh/liver.obj") - root.add(Visual, parameters) \ No newline at end of file + root.add(Visual, parameters=parameters) \ No newline at end of file