diff --git a/examples/stlib/EulalieScene.py b/examples/stlib/EulalieScene.py new file mode 100644 index 000000000..47d0ca2f8 --- /dev/null +++ b/examples/stlib/EulalieScene.py @@ -0,0 +1,14 @@ +from stlib.entities import Entity, EntityParameters +from stlib.modifiers import SingleEntityModifier +from stlib.modifiers.fixing import FixingModifierParameters +from stlib.settings.simulation import addSimulationSettings + + +def createScene(rootnode): + + simulation = addSimulationSettings(rootnode) + + cube1 = simulation.add(Entity, parameters=EntityParameters(name = "FixedCube")) + cube2 = simulation.add(Entity, parameters=EntityParameters(name = "FallingCube")) + + simulation.create(SingleEntityModifier, parameters=FixingModifierParameters()).apply(entity=cube1) \ No newline at end of file diff --git a/examples/stlib/SofaScene.py b/examples/stlib/SofaScene.py index 4e60200c6..67f667a5a 100644 --- a/examples/stlib/SofaScene.py +++ b/examples/stlib/SofaScene.py @@ -99,7 +99,7 @@ def createScene(root): SParams.material.parameters = [200, 0.45] def SAddMaterial(node): - DeformableBehaviorParameters.addDeformableMaterial(node) + DeformableBehaviorParameters.addMaterial(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..9896a259d 100644 --- a/splib/core/utils.py +++ b/splib/core/utils.py @@ -1,5 +1,7 @@ from typing import List, Callable, Tuple, Dict from functools import wraps +import Sofa +import Sofa.Core class defaultValueType(): def __init__(self): @@ -10,14 +12,12 @@ def __init__(self): 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) @@ -31,6 +31,24 @@ def wrapper(*args, **kwargs): return wrapper 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", default=False, 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", default=False, 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..883b6d698 100644 --- a/splib/mechanics/mass.py +++ b/splib/mechanics/mass.py @@ -1,17 +1,15 @@ from splib.core.node_wrapper import ReusableMethod -from splib.core.utils import defaultValueType, DEFAULT_VALUE, isDefault +from splib.core.utils import DEFAULT_VALUE, isDefault 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): + 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)) : diff --git a/stlib/__init__.py b/stlib/__init__.py index 0ad659858..3f5594daa 100644 --- a/stlib/__init__.py +++ b/stlib/__init__.py @@ -1,29 +1,35 @@ -__all__ = ["core","entities","geometries","materials","collision","visual"] +__all__ = ["core","entities","geometries","materials","modifiers","collision","visual"] import Sofa.Core from stlib.core.basePrefab import BasePrefab +from stlib.core.baseEntity import BaseEntity +from stlib.core.baseEntityModifier import BaseEntityModifier -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 +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 - 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 +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 - # Check if a name is provided, if not, use the one of the class + +def __processParameters(self : Sofa.Core.Node, typeName, **kwargs): + """Check if a name is provided, if not, use the one of the class + """ params = kwargs.copy() if isinstance(typeName, type) and issubclass(typeName, BasePrefab): #Only for prefabs if len(params.keys()) > 1 or (len(params.keys()) == 1 and "parameters" not in params): @@ -44,25 +50,56 @@ def checkName(context : Sofa.Core.Node, name): raise RuntimeError("Invalid argument ", typeName) if isinstance(typeName, type) and issubclass(typeName, BasePrefab) and len(params.keys()) == 1: - params["parameters"].name = checkName(self, params["parameters"].name) + params["parameters"].name = __checkName(self, params["parameters"].name) else: - params["name"] = checkName(self, params["name"]) + params["name"] = __checkName(self, params["name"]) + + return params + + +def __create(self: Sofa.Core.Node, typeName, **kwargs): + + params = __processParameters(self, typeName, **kwargs) + + if isinstance(typeName, type) and issubclass(typeName, BaseEntityModifier): + node = typeName(**params) + node.creator = self + return node + if isinstance(typeName, type) and issubclass(typeName, BasePrefab): + return typeName(**params) + elif isinstance(typeName, Sofa.Core.Node): + return typeName(**params) + + return + + +def __genericAdd(self: Sofa.Core.Node, typeName, **kwargs): # Dispatch the creation to either addObject or addChild if isinstance(typeName, type) and issubclass(typeName, BasePrefab): + params = __processParameters(self, typeName, **kwargs) pref = self.addChild(typeName(**params)) pref.init() + elif isinstance(typeName, Sofa.Core.Node) and issubclass(typeName.__class__, BaseEntity): + pref = self.addChild(typeName) + pref.init() elif isinstance(typeName, Sofa.Core.Node): - pref = self.addChild(typeName(**params)) + pref = self.addChild(typeName) elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.Object): + params = __processParameters(self, typeName, **kwargs) pref = self.addObject(typeName(**params)) elif isinstance(typeName, type) and issubclass(typeName, Sofa.Core.ObjectDeclaration): + params = __processParameters(self, typeName, **kwargs) pref = self.addObject(typeName.__name__, **params) elif isinstance(typeName, str): + params = __processParameters(self, typeName, **kwargs) 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 +Sofa.Core.Node.create = __create diff --git a/stlib/collision.py b/stlib/collision.py index b8f059249..2c674137c 100644 --- a/stlib/collision.py +++ b/stlib/collision.py @@ -5,7 +5,7 @@ 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): @@ -18,7 +18,7 @@ class CollisionParameters(BaseParameters): group : Optional[int] = DEFAULT_VALUE contactDistance : Optional[float] = DEFAULT_VALUE - geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) + geometry : GeometryParameters = GeometryParameters() class Collision(BasePrefab): @@ -28,7 +28,8 @@ def __init__(self, parameters: CollisionParameters): 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, @@ -36,26 +37,21 @@ def init(self): selfCollision=self.parameters.selfCollision, group=self.parameters.group, **self.parameters.kwargs) - - - @staticmethod - def getParameters(**kwargs) -> CollisionParameters: - return CollisionParameters(**kwargs) def createScene(root): - root.addObject("VisualStyle", displayFlags="showCollisionModels") + root.add("VisualStyle", displayFlags="showCollisionModels") # Create a visual from a mesh file - parameters = Collision.getParameters() + parameters = CollisionParameters() parameters.group = 1 parameters.geometry = FileParameters(filename="mesh/cube.obj") # Expert parameters # parameters.kwargs = { # "TriangleCollisionModel":{"contactStiffness": 100.0, "contactFriction": 0.5} # } - collision = root.add(Collision, parameters) + collision = root.add(Collision, parameters=parameters) # OR set the parameters post creation # collision.TriangleCollisionModel.contactStiffness = 100.0 diff --git a/stlib/core/baseEntity.py b/stlib/core/baseEntity.py index 1456436bb..f5cf7f00b 100644 --- a/stlib/core/baseEntity.py +++ b/stlib/core/baseEntity.py @@ -1,11 +1,9 @@ -import Sofa.Core -from baseParameters import BaseParameters +from stlib.core.basePrefab import BasePrefab +from stlib.core.baseParameters import BaseParameters -class BaseEntity(Sofa.Core.Prefab): +class BaseEntity(BasePrefab): - parameters : BaseParameters - - def __init__(self): - Sofa.Core.Prefab.__init__(self) + def __init__(self, parameters: BaseParameters): + BasePrefab.__init__(self, parameters=parameters) diff --git a/stlib/core/baseEntityModifier.py b/stlib/core/baseEntityModifier.py new file mode 100644 index 000000000..a72a787bd --- /dev/null +++ b/stlib/core/baseEntityModifier.py @@ -0,0 +1,24 @@ +from stlib.core.basePrefab import BasePrefab +import Sofa.Core + + +class BaseEntityModifier(BasePrefab): + """ + An EntityModifier is a Prefab that modifies a set of Entities + """ + + nodeName = "Modifiers" + creator : Sofa.Core.Node = None + + def __init__(self, parameters): + BasePrefab.__init__(self, parameters) + + def apply(self, **kwargs): + if not self.creator.getChild(self.nodeName): + self.creator.modifiers = self.creator.add(Sofa.Core.Node(self.nodeName)) + + self.creator.modifiers.add(self) + self._apply(**kwargs) + + def _apply(self, **kwargs): + raise NotImplemented("To be overridden by child class") \ No newline at end of file diff --git a/stlib/core/basePrefab.py b/stlib/core/basePrefab.py index 83d29f55d..0430175a8 100644 --- a/stlib/core/basePrefab.py +++ b/stlib/core/basePrefab.py @@ -1,13 +1,14 @@ -import copy import Sofa import Sofa.Core from stlib.core.basePrefabParameters import BasePrefabParameters class BasePrefab(Sofa.Core.Node): """ - A Prefab is a Sofa.Node that assembles a set of components and nodes + A Prefab is a Sofa.Core.Node that assembles a set of components and nodes """ + parameters : BasePrefabParameters + def __init__(self, parameters: BasePrefabParameters): Sofa.Core.Node.__init__(self, name=parameters.name) self.parameters = parameters @@ -15,9 +16,5 @@ def __init__(self, parameters: BasePrefabParameters): 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 bc7c76711..39771bf2a 100644 --- a/stlib/entities/__entity__.py +++ b/stlib/entities/__entity__.py @@ -1,35 +1,34 @@ from stlib.core.baseParameters import BaseParameters -from stlib.collision import CollisionParameters, Collision -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 splib.core.enum_types import StateType from stlib.core.basePrefab import BasePrefab +from stlib.geometries import Geometry +from stlib.geometries import GeometryParameters, InternalDataProvider +from stlib.geometries.file import FileParameters +from stlib.materials.rigid import RigidParameters +from stlib.materials import Material, MaterialParameters +from stlib.visual import VisualParameters, Visual +from stlib.collision import CollisionParameters, Collision +from splib.core.enum_types import ElementType + +import dataclasses +from typing import Optional + @dataclasses.dataclass class EntityParameters(BaseParameters): name : str = "Entity" - stateType : StateType = StateType.VEC3 + stateType : StateType = StateType.RIGID - ### QUID - addCollision : Optional[Callable] = lambda x : Collision(CollisionParameters()) - addVisual : Optional[Callable] = lambda x : Visual(VisualParameters()) - - geometry : GeometryParameters = None - material : MaterialParameters = None + geometry : GeometryParameters = GeometryParameters(elementType = ElementType.POINTS, data = InternalDataProvider(position = [[0., 0., 0.]])) + material : MaterialParameters = RigidParameters() + visual : Optional[VisualParameters] = VisualParameters(geometry = FileParameters(filename="mesh/cube.obj")) collision : Optional[CollisionParameters] = None - visual : Optional[VisualParameters] = None - - + class Entity(BasePrefab): - # A simulated object material : Material visual : Visual collision : Collision @@ -38,20 +37,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.checkMaterialCompatibility self.material = self.add(Material, parameters=self.parameters.material) - self.material.States.position.parent = self.geometry.container.position.linkpath + self.material.getMechanicalState().position.parent = self.geometry.container.position.linkpath if self.parameters.collision is not None: self.collision = self.add(Collision, parameters=self.parameters.collision) @@ -62,20 +57,27 @@ def init(self): self.addMapping(self.visual) - def addMapping(self, destinationPrefab): + def checkMaterialCompatibility(self): + 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 + + + def addMapping(self, destinationPrefab: BasePrefab): template = f'{self.parameters.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 + #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): destinationPrefab.addObject("BarycentricMapping", output=destinationPrefab.linkpath, output_topology=destinationPrefab.Geometry.container.linkpath, - input=self.Material.linkpath, - input_topology=self.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..f9abb1649 100644 --- a/stlib/geometries/__geometry__.py +++ b/stlib/geometries/__geometry__.py @@ -12,9 +12,9 @@ class Geometry(BasePrefab):... @dataclasses.dataclass -class InternalDataProvider(object): +class InternalDataProvider: position : Any = None - # Topology information + edges : Any = DEFAULT_VALUE triangles : Any = DEFAULT_VALUE quads : Any = DEFAULT_VALUE @@ -29,28 +29,19 @@ def generateAttribute(self, parent : Geometry): 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): # Generate attribute (positions, edges, triangles, quads, tetrahedra, hexahedra) from the internal data provider @@ -59,14 +50,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..3444692ce 100644 --- a/stlib/geometries/cube.py +++ b/stlib/geometries/cube.py @@ -1,14 +1,9 @@ from stlib.geometries import GeometryParameters +from stlib.geometries.file import FileParameters +import dataclasses -class CubeParameters(GeometryParameters): - def __init__(self, center, edgeLength, pointPerEdge, dynamicTopology = False): - customGeom = CubeParameters.createData(center, edgeLength, pointPerEdge) - GeometryParameters.__init__(data = customGeom, dynamicTopology = dynamicTopology) +@dataclasses.dataclass +class CubeParameters(FileParameters): - @staticmethod - def createData(center, edgeLength, pointPerEdge) -> GeometryParameters.Data : - data = GeometryParameters.Data() - #Fill data - return data - \ No newline at end of file + filename : str = "mesh/cube.obj" diff --git a/stlib/geometries/file.py b/stlib/geometries/file.py index 601782f9d..87e67e5d7 100644 --- a/stlib/geometries/file.py +++ b/stlib/geometries/file.py @@ -29,12 +29,13 @@ def generateAttribute(self, parent : Geometry): self.tetrahedra = str(parent.loader.tetrahedra.linkpath) - +@dataclasses.dataclass 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 __post_init__(self): + self.data = FileInternalDataProvider(filename=self.filename) diff --git a/stlib/materials/__material__.py b/stlib/materials/__material__.py index ff88dbd43..b1cd9af24 100644 --- a/stlib/materials/__material__.py +++ b/stlib/materials/__material__.py @@ -1,5 +1,5 @@ -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, dataclasses +from splib.core.utils import DEFAULT_VALUE from splib.core.enum_types import StateType from stlib.core.basePrefab import BasePrefab @@ -9,15 +9,14 @@ class MaterialParameters(BaseParameters): name : str = "Material" - massDensity : float = DEFAULT_VALUE + massDensity : float = 1e-6 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 @@ -27,5 +26,5 @@ def __init__(self, parameters: MaterialParameters): def init(self): - self.addObject("MechanicalObject", name="States", template=str(self.parameters.stateType)) + self.addObject("MechanicalObject", template=str(self.parameters.stateType)) self.parameters.addMaterial(self) diff --git a/stlib/materials/rigid.py b/stlib/materials/rigid.py index 5668b3562..a4096036f 100644 --- a/stlib/materials/rigid.py +++ b/stlib/materials/rigid.py @@ -1,13 +1,11 @@ -from stlib.core.baseParameters import BaseParameters, Optional, dataclasses -from stlib.geometries import GeometryParameters - +from stlib.core.baseParameters import Optional, dataclasses +from stlib.materials import MaterialParameters +from splib.core.utils import DEFAULT_VALUE +from splib.core.enum_types import StateType @dataclasses.dataclass -class RigidParameters(BaseParameters): - - geometry : GeometryParameters - mass : Optional[float] = None +class RigidParameters(MaterialParameters): + + stateType : StateType = StateType.RIGID - def toDict(self): - return dataclasses.asdict(self) diff --git a/stlib/modifiers/__init__.py b/stlib/modifiers/__init__.py new file mode 100644 index 000000000..186ce5b68 --- /dev/null +++ b/stlib/modifiers/__init__.py @@ -0,0 +1 @@ +from .__modifier__ import * \ No newline at end of file diff --git a/stlib/modifiers/__modifier__.py b/stlib/modifiers/__modifier__.py new file mode 100644 index 000000000..cfec85060 --- /dev/null +++ b/stlib/modifiers/__modifier__.py @@ -0,0 +1,24 @@ +from stlib.core.baseParameters import BaseParameters +from stlib.core.baseEntityModifier import BaseEntityModifier +from stlib.entities import Entity +import dataclasses + + +@dataclasses.dataclass +class ModifierParameters(BaseParameters): + + name : str = "Modifier" + creator : str = None + + def modifier(entity: Entity): + pass + + +class SingleEntityModifier(BaseEntityModifier): + + def __init__(self, parameters: ModifierParameters): + BaseEntityModifier.__init__(self, parameters) + + def _apply(self, entity: Entity): + entity.add(self) + self.parameters.modifier(entity) \ No newline at end of file diff --git a/stlib/modifiers/fixing.py b/stlib/modifiers/fixing.py new file mode 100644 index 000000000..b354c66f0 --- /dev/null +++ b/stlib/modifiers/fixing.py @@ -0,0 +1,13 @@ +from stlib.modifiers import ModifierParameters +from stlib.entities import Entity +from stlib.core.baseParameters import Optional, dataclasses +from splib.core.utils import DEFAULT_VALUE + +@dataclasses.dataclass +class FixingModifierParameters(ModifierParameters): + + name : str = "FixingModifier" + indices : Optional[list[int]] = DEFAULT_VALUE + + def modifier(self, entity: Entity): + entity.material.addObject("FixedProjectiveConstraint", indices=self.indices) \ 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..d6c739808 --- /dev/null +++ b/stlib/settings/simulation.py @@ -0,0 +1,31 @@ +import Sofa.Core +from splib.core.utils import REQUIRES_COLLISIONPIPELINE, REQUIRES_LAGRANGIANCONSTRAINTSOLVER + +def addSimulationSettings(rootnode: Sofa.Core.Node): + + rootnode.add('DefaultVisualManagerLoop') + 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') + + simulation = rootnode.add(Sofa.Core.Node('Simulation')) + + simulation.add('EulerImplicitSolver') + simulation.add('SparseLDLSolver') + + if rootnode.findData(REQUIRES_LAGRANGIANCONSTRAINTSOLVER) and rootnode.findData(REQUIRES_LAGRANGIANCONSTRAINTSOLVER).value: + simulation.add('GenericConstraintCorrection') + + return simulation + diff --git a/stlib/visual.py b/stlib/visual.py index a0f377f7d..0a6d4ee3b 100644 --- a/stlib/visual.py +++ b/stlib/visual.py @@ -12,7 +12,7 @@ class VisualParameters(BaseParameters): color : Optional[list[float]] = DEFAULT_VALUE texture : Optional[str] = DEFAULT_VALUE - geometry : GeometryParameters = dataclasses.field(default_factory = lambda : GeometryParameters()) + geometry : 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