Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions examples/stlib/EulalieScene.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion examples/stlib/SofaScene.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
22 changes: 20 additions & 2 deletions splib/core/utils.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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)
Expand All @@ -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




8 changes: 3 additions & 5 deletions splib/mechanics/mass.py
Original file line number Diff line number Diff line change
@@ -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)) :
Expand Down
79 changes: 58 additions & 21 deletions stlib/__init__.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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
18 changes: 7 additions & 11 deletions stlib/collision.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -28,34 +28,30 @@ 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,
topology=f"@{self.parameters.geometry.name}/container",
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
Expand Down
12 changes: 5 additions & 7 deletions stlib/core/baseEntity.py
Original file line number Diff line number Diff line change
@@ -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)


24 changes: 24 additions & 0 deletions stlib/core/baseEntityModifier.py
Original file line number Diff line number Diff line change
@@ -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")
9 changes: 3 additions & 6 deletions stlib/core/basePrefab.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
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

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 :)")



Loading
Loading