Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d6d0794
add folder for STLIB
bakpaul Apr 14, 2025
8df6143
Add reusable methods
bakpaul Apr 14, 2025
778d912
Add working files
bakpaul Apr 14, 2025
31ee7a7
Created example scenes
bakpaul Apr 14, 2025
59bb89b
Fix example
bakpaul Apr 14, 2025
21c0af2
orga folders
hugtalbot Apr 14, 2025
ec2b2c3
Fix package
bakpaul Apr 14, 2025
73849a3
bootstrap examples
bakpaul Apr 14, 2025
86e9a0d
start beginner scene
hugtalbot Apr 14, 2025
f8050b3
Move around files
bakpaul Apr 14, 2025
e597d65
Move examples into SofaPython3 main example folder
bakpaul Apr 14, 2025
ee70863
Add unsaved modifications
bakpaul Apr 14, 2025
a30190f
reoganisation after discussion
EulalieCoevoet Apr 14, 2025
63b2f72
Add gemetries
bakpaul Apr 14, 2025
690d6c9
WIP FileParameters
bakpaul Apr 14, 2025
71232f4
Add beginner example
bakpaul Apr 14, 2025
cdea524
Add unsaved changes
bakpaul Apr 14, 2025
6896284
Start work on visual and collision
bakpaul Apr 14, 2025
4b3df06
work on Tuesday : visual, geometry
hugtalbot Apr 15, 2025
763b4ac
add basePrefabParameters.py
hugtalbot Apr 15, 2025
4352fc2
Add extracted geometry
bakpaul Apr 15, 2025
ade7fa7
Fix visual example
bakpaul Apr 15, 2025
1516840
Finished collisions
bakpaul Apr 15, 2025
37aee53
[stlib-splib] work on prefabs: cleaning (#501)
EulalieCoevoet Jun 2, 2025
3b98ae4
[stlib] adds README
EulalieCoevoet Jun 2, 2025
2a55a36
[stlib] minor changes from discussion
EulalieCoevoet Jun 2, 2025
d40d019
[splib] updates entity
EulalieCoevoet Jun 3, 2025
666d8e8
update README
hugtalbot Jun 3, 2025
1c32b92
add init in prefab
hugtalbot Jun 3, 2025
879b59d
Add first implem of entity
bakpaul Jun 3, 2025
7b62fb2
before the train 3/3
hugtalbot Jun 3, 2025
c64a3f6
very last commit befor train
EulalieCoevoet Jun 3, 2025
5d23215
WIP
bakpaul Jun 3, 2025
4ce1fba
Retore original. Still crash due to wrong string conversion
bakpaul Jun 3, 2025
ab456df
[stlib] Deformable Entity (#502)
EulalieCoevoet Jun 13, 2025
aaa8c1d
Fix linkpath by introducing init method + make addDeformableMaterial …
bakpaul Jun 13, 2025
2465a82
[STLIB] Add the unified "add" in Sofa.Core.Node (#503)
damienmarchal Jul 17, 2025
d066293
Add new tests for prefabs
bakpaul Jul 17, 2025
80be8cb
Revert modification of __genericAdd
bakpaul Jul 17, 2025
ae77763
update addMass using elementType
hugtalbot Jul 17, 2025
266a6ce
[Prefabs] Reorder files to match concepts (#547)
bakpaul Oct 28, 2025
12baea2
[Prefabs] cleaning to fix deformable example (#549)
EulalieCoevoet Oct 28, 2025
d850a24
Fix by passing state link when using other topology than edges (#550)
bakpaul Oct 28, 2025
8509991
Fix constraint solvr name
bakpaul Mar 2, 2026
92fc7fb
Register in the PythonFactory Rigid3::Coord (#544)
damienmarchal Oct 30, 2025
ec84018
Squashed commit of the following:
bakpaul Mar 3, 2026
8b239c6
revert some changes
EulalieCoevoet Mar 3, 2026
aee9423
dataclasses to pydantic BaseModel
EulalieCoevoet Apr 9, 2026
b2eff82
Merge branch 'master' into pr_basemodelpydantic
EulalieCoevoet Apr 20, 2026
9ee6042
list of changes:
EulalieCoevoet Apr 23, 2026
559753a
adds docs and prefab class
EulalieCoevoet May 19, 2026
63966a5
moves cube, sphere, bunny to entity directory, refactor FileInternalD…
EulalieCoevoet May 19, 2026
7e288d4
Merge branch '25_04_work_on_new_prefabs' into pr_basemodelpydantic
EulalieCoevoet May 19, 2026
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
8 changes: 4 additions & 4 deletions examples/stlib/SofaScene.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")))

Expand Down Expand Up @@ -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)

Expand Down
19 changes: 19 additions & 0 deletions splib/core/utils.py
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot that we wanted to have this. At one point we'll need to merge this with the node modifier part.

Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,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



15 changes: 9 additions & 6 deletions splib/mechanics/mass.py
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated but fine by me.

Original file line number Diff line number Diff line change
Expand Up @@ -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)

2 changes: 1 addition & 1 deletion stlib/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 8 additions & 5 deletions stlib/collision.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
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)

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,
Expand Down
8 changes: 4 additions & 4 deletions stlib/core/baseEntity.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import Sofa.Core
from baseParameters import BaseParameters
import Sofa
from stlib.core.baseParameters import BaseParameters

class BaseEntity(Sofa.Core.Prefab):
class BaseEntity(Sofa.Prefab):

parameters : BaseParameters

def __init__(self):
Sofa.Core.Prefab.__init__(self)
Sofa.Prefab.__init__(self)


35 changes: 28 additions & 7 deletions stlib/core/baseParameters.py
Original file line number Diff line number Diff line change
@@ -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)
28 changes: 17 additions & 11 deletions stlib/core/basePrefab.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import copy
import Sofa
import Sofa.Core
from stlib.core.basePrefabParameters import BasePrefabParameters

from stlib.core.baseParameters import BaseParameters


class BasePrefabParameters(BaseParameters):
name : str = "object"
kwargs : 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.]


class BasePrefab(Sofa.Core.Node):
"""
A Prefab is a Sofa.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")
raise NotImplemented("This method should be implemented in the child class to initialize the prefab's components and nodes based on the provided parameters.")


def localToGlobalCoordinates(pointCloudInput, pointCloudOutput):
raise NotImplemented("Send an email to Damien, he will help you. Guaranteed :)")



15 changes: 0 additions & 15 deletions stlib/core/basePrefabParameters.py

This file was deleted.

67 changes: 40 additions & 27 deletions stlib/entities/__entity__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,69 @@
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):
"""
Attributes:
name (str): The name of the entity.
addCollision (Optional[Callable]): Optional callable to add a collision component to the entity.
addVisual (Optional[Callable]): Optional callable to add a visual component to the entity.
geometry (GeometryParameters): Parameters for the geometry of the entity, with a default of a point at the origin.
material (MaterialParameters): Parameters for the material of the entity, with a default of a rigid material.
visual (Optional[VisualParameters]): Optional parameters for the visual component of the entity, with a default of a cube mesh.
collision (Optional[CollisionParameters]): Optional parameters for the collision component of the entity, with a default of None.
"""
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()
visual : Optional[VisualParameters] = VisualParameters(geometry = FileParameters(filename="mesh/cube.obj"))
collision : Optional[CollisionParameters] = None
visual : Optional[VisualParameters] = None



class Entity(BasePrefab):
"""
An entity is a Prefab, representing a physical object with geometry, material properties,
and optional visual and collision components. It serves as a base class for more complex entities in the simulation.

Attributes:
parameters (EntityParameters): The parameters defining the entity, including its name, geometry, material properties, and optional visual and collision components.
"""

# A simulated object
geometry : Geometry
material : Material
visual : Visual
collision : Collision
geometry : Geometry

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)
Expand All @@ -64,18 +77,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)
27 changes: 27 additions & 0 deletions stlib/entities/bunny.py
Original file line number Diff line number Diff line change
@@ -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)
Loading
Loading