Skip to content

Commit ec84018

Browse files
committed
Squashed commit of the following:
commit b0cffe7 Author: bakpaul <paul.baksic@outlook.fr> Date: Tue Mar 3 10:17:06 2026 +0100 Dump ideas commit 4baade5 Author: Damien Marchal <damien.marchal@univ-lille1.fr> Date: Thu Oct 30 16:06:25 2025 +0100 Register in the PythonFactory Rigid3::Coord (#544) Because currently only Rigid3::VecCoord was supported. commit dd6865e Author: bakpaul <paul.baksic@outlook.fr> Date: Mon Mar 2 14:26:25 2026 +0100 Make exctracted topo working' commit de007a3 Author: Paul Baksic <paul.baksic@outlook.fr> Date: Wed Oct 29 17:31:41 2025 +0100 Added S, but still need to make the free motion not crash commit d3148a5 Author: Paul Baksic <paul.baksic@outlook.fr> Date: Wed Oct 29 12:21:41 2025 +0100 Fix plane and some splib deprecated components commit d2c120e Author: Paul Baksic <paul.baksic@outlook.fr> Date: Tue Oct 28 18:22:41 2025 +0100 Add plane, still need to really implement function + started creating the scene
1 parent 92fc7fb commit ec84018

11 files changed

Lines changed: 221 additions & 27 deletions

File tree

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ SP3_add_python_package(
203203
TARGET_DIRECTORY
204204
splib
205205
)
206+
SP3_add_python_package(
207+
SOURCE_DIRECTORY
208+
${CMAKE_CURRENT_SOURCE_DIR}/stlib
209+
TARGET_DIRECTORY
210+
stlib
211+
)
206212

207213
sofa_create_package(
208214
PACKAGE_NAME ${PROJECT_NAME}

examples/stlib/SofaScene.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from fontTools.afmLib import preferredAttributeOrder
2+
3+
from stlib.geometries.plane import PlaneParameters
4+
from stlib.geometries.file import FileParameters
5+
from stlib.geometries.extract import ExtractParameters
6+
from stlib.materials.deformable import DeformableBehaviorParameters
7+
from stlib.collision import Collision, CollisionParameters
8+
from stlib.entities import Entity, EntityParameters
9+
from stlib.visual import Visual, VisualParameters
10+
from splib.core.enum_types import CollisionPrimitive, ElementType, ConstitutiveLaw
11+
from splib.simulation.headers import setupLagrangianCollision, setupDefaultHeader
12+
from splib.simulation.ode_solvers import addImplicitODE
13+
from splib.simulation.linear_solvers import addLinearSolver
14+
import dataclasses
15+
import numpy as np
16+
17+
18+
def createScene(root):
19+
root.gravity=[0,0,9.81]
20+
##Solvers
21+
# setupDefaultHeader(root, displayFlags = "showVisualModels",backgroundColor=[0.8, 0.8, 0.8, 1],
22+
# parallelComputing = True)
23+
setupLagrangianCollision(root, displayFlags = "showVisualModels",backgroundColor=[0.8, 0.8, 0.8, 1],
24+
parallelComputing = True,alarmDistance=0.3, contactDistance=0.02,
25+
frictionCoef=0.5, tolerance=1.0e-4, maxIterations=20)
26+
##Environement
27+
planes_lengthNormal = np.array([0, 1, 0])
28+
planes_lengthNbEdge = 1
29+
planes_widthNbEdge = 2
30+
planes_lengthSize = 30
31+
planes_widthSize = 70
32+
33+
plane1_collisionParams = CollisionParameters()
34+
plane1_collisionParams.name = "UP"
35+
plane1_collisionParams.primitives = [CollisionPrimitive.TRIANGLES]
36+
plane1_collisionParams.kwargs = {"TriangleCollision" : {"moving" : False, "simulated" : False}}
37+
plane1_collisionParams.geometry = PlaneParameters(np.array([15,0,1]), np.array([0,0,-1]),
38+
planes_lengthNormal, planes_lengthNbEdge, planes_widthNbEdge, planes_lengthSize, planes_widthSize)
39+
plane1 = root.add(Collision, parameters = plane1_collisionParams)
40+
# TODO being able to reuse already loaded geometry of current prefab to add any new sub prefab
41+
# We need to enable to directly pass a link to an already existing prefab in place of a prefab parameter object
42+
plane1_visu = plane1.addChild("Visu")
43+
plane1_visu.addObject("OglModel", name="VisualModel", src="@../Geometry/container")
44+
45+
46+
plane2_collisionParams = CollisionParameters()
47+
plane2_collisionParams.name = "DOWN"
48+
plane2_collisionParams.primitives = [CollisionPrimitive.TRIANGLES]
49+
plane2_collisionParams.kwargs = {"TriangleCollision" : {"moving" : False, "simulated" : False}}
50+
plane2_collisionParams.geometry = PlaneParameters(np.array([15,0,-20]), np.array([0,0,1]),
51+
planes_lengthNormal, planes_lengthNbEdge, planes_widthNbEdge, planes_lengthSize, planes_widthSize)
52+
plane2 = root.add(Collision, parameters = plane2_collisionParams)
53+
plane2_visu = plane2.addChild("Visu")
54+
plane2_visu.addObject("OglModel", name="VisualModel", src="@../Geometry/container")
55+
56+
57+
## Real models
58+
# VolumetricObjects = root.addChild("VolumetricObjects")
59+
# addImplicitODE(VolumetricObjects)
60+
# addLinearSolver(VolumetricObjects, constantSparsity=False, )
61+
62+
### Logo
63+
LogoNode = root.addChild("LogoNode")
64+
addImplicitODE(LogoNode)
65+
addLinearSolver(LogoNode, constantSparsity=False, )
66+
67+
LogoParams = EntityParameters(name = "Logo",
68+
geometry = FileParameters(filename="mesh/SofaScene/Logo.vtk"),
69+
material = DeformableBehaviorParameters(),
70+
collision = CollisionParameters(geometry = FileParameters(filename="mesh/SofaScene/LogoColli.sph")),
71+
visual = VisualParameters(geometry = FileParameters(filename="mesh/SofaScene/LogoVisu.obj")))
72+
73+
LogoParams.geometry.elementType = ElementType.TETRAHEDRA
74+
LogoParams.material.constitutiveLawType = ConstitutiveLaw.ELASTIC
75+
LogoParams.material.parameters = [200, 0.4]
76+
LogoParams.material.massDensity = 0.003261
77+
LogoParams.collision.primitives = [CollisionPrimitive.SPHERES]
78+
#TODO make this flawless with spheres. Here collisions elements are not in the topology and a link is to be made between the loader and the collision object
79+
LogoParams.collision.kwargs = {"SphereCollision" : {"radius" : "@Geometry/loader.listRadius"}}
80+
LogoParams.visual.color = [0.7, .35, 0, 0.8]
81+
82+
Logo = LogoNode.add(Entity, parameters = LogoParams)
83+
84+
Logo.material.addObject("ConstantForceField", name="ConstantForceUpwards", totalForce=[0, 0, -5.0])
85+
Logo.material.addObject("LinearSolverConstraintCorrection", name="ConstraintCorrection", linearSolver=LogoNode.LinearSolver.linkpath, ODESolver=LogoNode.ODESolver.linkpath)
86+
87+
88+
### S
89+
SNode = root.addChild("SNode")
90+
addImplicitODE(SNode)
91+
addLinearSolver(SNode, constantSparsity=False, )
92+
93+
SParams = EntityParameters("bob.yaml")
94+
SParams.name = "S"
95+
SParams.geometry = FileParameters(filename="mesh/SofaScene/S.vtk")
96+
SParams.geometry.elementType = ElementType.TETRAHEDRA
97+
SParams.material = DeformableBehaviorParameters()
98+
SParams.material.constitutiveLawType = ConstitutiveLaw.ELASTIC
99+
SParams.material.parameters = [200, 0.45]
100+
101+
def SAddMaterial(node):
102+
DeformableBehaviorParameters.addDeformableMaterial(node)
103+
#TODO deal with that is a more smooth way in the material directly
104+
node.addObject("LinearSolverConstraintCorrection", name="ConstraintCorrection", linearSolver=SNode.LinearSolver.linkpath, ODESolver=SNode.ODESolver.linkpath)
105+
106+
SParams.material.addMaterial = SAddMaterial
107+
SParams.material.massDensity = 0.011021
108+
SParams.collision = CollisionParameters()
109+
SParams.collision.primitives = [CollisionPrimitive.TRIANGLES]
110+
# # #TODO: to fix link issues for extracted geometry, it might be better to give source geometry relative link + parameters
111+
SParams.collision.geometry = ExtractParameters(destinationType=ElementType.TRIANGLES, sourceParameters=SParams.geometry )
112+
SParams.visual = VisualParameters()
113+
SParams.visual.geometry = FileParameters(filename="mesh/SofaScene/SVisu.obj")
114+
SParams.visual.color = [0.7, .7, 0.7, 0.8]
115+
116+
S = SNode.add(Entity, parameters = SParams)
117+
118+
119+
SDensity = 0.011021
120+
ODensity = SDensity
121+
ADensity = 0.00693695

splib/core/node_wrapper.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ def addObject(self,*args, **kwargs):
2525
parameters["name"] = kwargs["name"]
2626
if kwargs["name"] in kwargs:
2727
if isinstance(kwargs[kwargs["name"]], dict):
28-
parameters = {**parameters, **kwargs[kwargs["name"]]}
28+
for param in kwargs[kwargs["name"]]:
29+
if not(isinstance(kwargs[kwargs["name"]][param],defaultValueType)):
30+
parameters = {**parameters, param : kwargs[kwargs["name"]][param]}
2931
else:
3032
print("[Warning] You are passing a keyword arg with the same name as one obj without it being a Dict, it will not be used. ")
3133

splib/mechanics/collision_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def addCollisionModels(node, primitive : CollisionPrimitive,
2424
node.addObject("TriangleCollisionModel", name="TriangleCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group,**kwargs)
2525
return
2626
case CollisionPrimitive.SPHERES:
27-
node.addObject("SphereCollisionModel", name="SphereCollision", topology=topology, selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, radius=spheresRadius, **kwargs)
27+
node.addObject("SphereCollisionModel", name="SphereCollision", selfCollision=selfCollision, proximity=proximity, contactStiffness=contactStiffness, contactFriction=contactFriction, group=group, radius=spheresRadius, **kwargs)
2828
return
2929
case _:
3030
return

stlib/collision.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(self, parameters: CollisionParameters):
2727

2828
def init(self):
2929

30-
geom = self.add(Geometry, self.parameters.geometry)
30+
geom = self.add(Geometry, parameters = self.parameters.geometry)
3131

3232
self.addObject("MechanicalObject", template="Vec3", position=f"@{self.parameters.geometry.name}/container.position")
3333
for primitive in self.parameters.primitives:

stlib/entities/__entity__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,17 @@ def init(self):
6565
def addMapping(self, destinationPrefab):
6666

6767
template = f'{self.parameters.stateType},Vec3' # TODO: check that it is always true
68-
68+
69+
#TODO: all paths are expecting Geometry to be called Geomtry and so on. We need to robustify this by using the name parameter somehow
6970
if( self.parameters.stateType == StateType.VEC3):
7071
destinationPrefab.addObject("BarycentricMapping",
7172
output=destinationPrefab.linkpath,
72-
output_topology=destinationPrefab.geometry.container.linkpath,
73-
input=self.material.linkpath,
74-
input_topology=self.geometry.container.linkpath,
73+
output_topology=destinationPrefab.Geometry.container.linkpath,
74+
input=self.Material.linkpath,
75+
input_topology=self.Geometry.container.linkpath,
7576
template=template)
7677
else:
7778
destinationPrefab.addObject("RigidMapping",
7879
output=destinationPrefab.linkpath,
79-
input=self.material.linkpath,
80+
input=self.Material.linkpath,
8081
template=template)

stlib/geometries/__geometry__.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from splib.topology.static import addStaticTopology
55
from splib.core.enum_types import ElementType
66
from splib.core.utils import DEFAULT_VALUE
7-
from Sofa.Core import Object
7+
from Sofa.Core import Object
8+
9+
import numpy as np
810

911

1012
class Geometry(BasePrefab):...
@@ -33,6 +35,10 @@ class GeometryParameters(BaseParameters):
3335

3436
dynamicTopology : bool = False
3537

38+
def Data(self):
39+
return InternalDataProvider()
40+
41+
3642

3743
class Geometry(BasePrefab):
3844
# container : Object # This should be more specialized into the right SOFA type
@@ -48,11 +54,19 @@ def __init__(self, parameters: GeometryParameters):
4854
def init(self):
4955

5056
# Generate attribute (positions, edges, triangles, quads, tetrahedra, hexahedra) from the internal data provider
51-
if self.parameters.data is not None :
57+
if isinstance(self.parameters.data, InternalDataProvider) :
5258
self.parameters.data.generateAttribute(self)
59+
5360
if self.parameters.dynamicTopology :
5461
if self.parameters.elementType is not None :
55-
addDynamicTopology(self, container = dataclasses.asdict(self.parameters.data))
62+
addDynamicTopology(self, elementType=self.parameters.elementType, container = {
63+
"position": self.parameters.data.position,
64+
"edges": self.parameters.data.edges,
65+
"triangles": self.parameters.data.triangles,
66+
"quads": self.parameters.data.quads,
67+
"tetrahedra": self.parameters.data.tetrahedra,
68+
"hexahedra": self.parameters.data.hexahedra
69+
})
5670
else:
5771
raise ValueError
5872
else:

stlib/geometries/extract.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __post_init__(self):
2525

2626
InternalDataProvider.__init__(self)
2727

28-
def generateAttribute(self, parent : Geometry):
28+
def generateAttribute(self, parent : Geometry):
2929
node = parent.addChild("ExtractedGeometry")
3030

3131
#TODO: Specify somewhere in the doc that this should only be used for mapped topologies that extract parent topology surface
@@ -34,8 +34,8 @@ def generateAttribute(self, parent : Geometry):
3434
# !!! also, on a fail, nothing is added to the graph, which makes things harder to debug
3535
# !!! also, does not work because of the function canCreate(), which checks the input (not yet created?)
3636
# this is all related
37-
fromLink = "@../../Geometry.container" # TODO: can we do better than this?
38-
addDynamicTopology(node, elementType=self.sourceType)
37+
fromLink = "@../../../Geometry/container" # TODO: can we do better than this?
38+
addDynamicTopology(node, elementType=self.destinationType, container={"position" : fromLink + ".position"})
3939
if self.sourceType == ElementType.TETRAHEDRA:
4040
node.addObject("Tetra2TriangleTopologicalMapping", input=fromLink, output=node.container.linkpath)
4141
elif self.sourceType == ElementType.HEXAHEDRA:
@@ -60,12 +60,11 @@ def generateAttribute(self, parent : Geometry):
6060
class ExtractParameters(GeometryParameters):
6161
def __init__(self,
6262
sourceParameters : GeometryParameters,
63-
destinationType : ElementType,
64-
dynamicTopology : bool = False, ):
63+
destinationType : ElementType,):
6564
GeometryParameters.__init__(self,
6665
data = ExtractInternalDataProvider(destinationType = destinationType,
6766
sourceType = sourceParameters.elementType,
6867
sourceName = sourceParameters.name),
69-
dynamicTopology = dynamicTopology,
68+
dynamicTopology = True,
7069
elementType = destinationType)
7170

stlib/geometries/file.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@ def __post_init__(self, **kwargs):
1313
InternalDataProvider.__init__(self,**kwargs)
1414

1515
def generateAttribute(self, parent : Geometry):
16-
loadMesh(parent, self.filename)
17-
18-
self.position = str(parent.loader.position.linkpath)
19-
self.edges = str(parent.loader.edges.linkpath)
20-
self.triangles = str(parent.loader.triangles.linkpath)
21-
self.quads = str(parent.loader.quads.linkpath)
22-
self.hexahedra = str(parent.loader.hexahedra.linkpath)
23-
self.tetrahedra = str(parent.loader.tetras.linkpath)
16+
loadMesh(parent, self.filename)
17+
18+
if hasattr(parent.loader, 'position'):
19+
self.position = str(parent.loader.position.linkpath)
20+
if hasattr(parent.loader, 'edges'):
21+
self.edges = str(parent.loader.edges.linkpath)
22+
if hasattr(parent.loader, 'triangles'):
23+
self.triangles = str(parent.loader.triangles.linkpath)
24+
if hasattr(parent.loader, 'quads'):
25+
self.quads = str(parent.loader.quads.linkpath)
26+
if hasattr(parent.loader, 'hexahedra'):
27+
self.hexahedra = str(parent.loader.hexahedra.linkpath)
28+
if hasattr(parent.loader, 'tetrahedra'):
29+
self.tetrahedra = str(parent.loader.tetrahedra.linkpath)
2430

2531

2632

stlib/geometries/plane.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from stlib.geometries import GeometryParameters, InternalDataProvider, Geometry
2+
import dataclasses
3+
import numpy as np
4+
5+
@dataclasses.dataclass
6+
class PlaneDataProvider(InternalDataProvider):
7+
center : np.ndarray[float] = dataclasses.field(default_factory = lambda : np.array([0,0,0]))
8+
normal : np.ndarray[float] = dataclasses.field(default_factory = lambda : np.array([0,0,1]))
9+
lengthNormal : np.ndarray[float] = dataclasses.field(default_factory = lambda : np.array([1,0,0]))
10+
lengthNbEdge : int = 1
11+
widthNbEdge : int = 1
12+
lengthSize : float = 1.0
13+
widthSize : float = 1.0
14+
15+
def __post_init__(self, **kwargs):
16+
InternalDataProvider.__init__(self,**kwargs)
17+
18+
def generateAttribute(self, parent : Geometry):
19+
20+
lengthEdgeSize = self.lengthSize / self.lengthNbEdge
21+
widthEdgeSize = self.widthSize / self.widthNbEdge
22+
23+
self.widthNormal = np.cross(self.normal,self.lengthNormal)
24+
bottomLeftCorner = self.center - self.lengthNormal * self.lengthNbEdge * lengthEdgeSize / 2 - self.widthNormal * self.widthNbEdge * widthEdgeSize / 2
25+
26+
self.position = np.array([[ bottomLeftCorner + j * self.widthNormal * widthEdgeSize + i * self.lengthNormal * lengthEdgeSize for j in range(self.widthNbEdge + 1) ] for i in range(self.lengthNbEdge + 1)])
27+
28+
self.triangles = np.empty((2 * self.widthNbEdge * self.lengthNbEdge, 3), dtype = int)
29+
for i in range(self.lengthNbEdge):
30+
for j in range(self.widthNbEdge):
31+
self.triangles[i*self.widthNbEdge*2 + j * 2 , 0] = j + i * (self.widthNbEdge + 1)
32+
self.triangles[i*self.widthNbEdge*2 + j * 2 , 1] = j + (i+1) * (self.widthNbEdge + 1)
33+
self.triangles[i*self.widthNbEdge*2 + j * 2 , 2] = j + 1 + i * (self.widthNbEdge + 1)
34+
self.triangles[i*self.widthNbEdge*2 + j * 2 + 1, 0] = j + 1 + i * (self.widthNbEdge + 1)
35+
self.triangles[i*self.widthNbEdge*2 + j * 2 + 1, 1] = j + (i+1) * (self.widthNbEdge + 1)
36+
self.triangles[i*self.widthNbEdge*2 + j * 2 + 1, 2] = j + 1 + (i+1) * (self.widthNbEdge + 1)
37+
38+
39+
40+
41+
class PlaneParameters(GeometryParameters):
42+
43+
def __init__(self, center, normal, lengthNormal, lengthNbEdge, widthNbEdge, lengthSize, widthSize, dynamicTopology = False):
44+
GeometryParameters.__init__(self, data = PlaneDataProvider(center=center, normal=normal, lengthNormal=lengthNormal, lengthNbEdge=lengthNbEdge, widthNbEdge=widthNbEdge, lengthSize=lengthSize, widthSize=widthSize),
45+
dynamicTopology = dynamicTopology)

0 commit comments

Comments
 (0)