Skip to content

Commit 475b564

Browse files
committed
Add a spot model
All the assets are in (which is questionnable)
1 parent 5dd9eef commit 475b564

27 files changed

+64635
-0
lines changed

live-example/models/compliancerobotics/__init__.py

Whitespace-only changes.

live-example/models/compliancerobotics/robots/__init__.py

Whitespace-only changes.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from stlib.entities import Entity
2+
from compliancerobotics.robots.spot.__parameters__ import SpotRobotParameters
3+
import numpy as np
4+
5+
class SpotRobot(Entity):
6+
"""Boston dynamic spot robots"""
7+
@staticmethod
8+
def get_asset(name) -> str:
9+
import os
10+
return os.path.join(os.path.join(os.path.dirname(__file__), "assets"), name)
11+
12+
@staticmethod
13+
def getParameters(**kwargs) -> SpotRobotParameters:
14+
return SpotRobotParameters(**kwargs)
15+
16+
def __init__(self, parameter : SpotRobotParameters = None, **kwargs):
17+
if parameter is None:
18+
parameter = SpotRobotParameters(**kwargs)
19+
Entity.__init__(self, parameter)
20+
21+
self.create(parameter)
22+
23+
def create(self, parameter):
24+
# Robot node
25+
settings = self.addChild('settings')
26+
settings.addObject("RequiredPlugin", name="SoftRobots")
27+
settings.addObject("RequiredPlugin", name="Sofa.RigidBodyDynamics") # Needed to use components [URDFModelLoader]
28+
settings.addObject('RequiredPlugin', name='Sofa.Component.Mapping.NonLinear') # Needed to use components [RigidMapping]
29+
settings.addObject('RequiredPlugin', name='Sofa.Component.Mass') # Needed to use components [UniformMass]
30+
settings.addObject('RequiredPlugin', name='Sofa.Component.Topology.Container.Constant') # Needed to use components [MeshTopology]
31+
settings.addObject('RequiredPlugin', name='Sofa.GL.Component.Rendering3D') # Needed to use components [OglModel]
32+
33+
self.addObject('URDFModelLoader',
34+
filename=SpotRobot.get_asset("model.urdf"),
35+
modelDirectory=SpotRobot.get_asset(""),
36+
useFreeFlyerRootJoint=False,
37+
printLog=False,
38+
addCollision=False,
39+
addJointsActuators=False)
40+
41+
robot = self.getChild("Robot")
42+
robot.name = "model"
43+
mechanical = robot.Model.getMechanicalState()
44+
mechanical.showObject = False
45+
mechanical.showObjectScale = 0.01
46+
mechanical.drawMode = 0
47+
48+
# Direct problem
49+
names = robot.Joints.children
50+
positions = np.copy(robot.getMechanicalState().position.value)
51+
for i in range(len(positions)):
52+
jointName = names[i+1].name.value
53+
value = 0 if jointName not in parameter.spot_ctrl_joint_infos else parameter.spot_ctrl_joint_infos[jointName].pos_desired
54+
positions[i] = value
55+
joint = robot.addObject('JointConstraint', template='Vec1', name='joint' + jointName, index=i,
56+
valueType="angle",
57+
value=value
58+
)
59+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from __entity__ import SpotRobot
2+
from __scene__ import createScene as ExampleScene
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from stlib.core.baseParameters import BaseParameters, dataclasses
2+
3+
@dataclasses.dataclass
4+
class ControlJointValue:
5+
Kp: float = 0.0
6+
Kd: float = 0.0
7+
Ki: float = 0.0
8+
i_clamp: float = 0.0
9+
pos_desired: float = 0.0
10+
vel_desired: float = 0.0
11+
12+
@dataclasses.dataclass
13+
class SpotRobotParameters(BaseParameters):
14+
name : str = "SpotRobot"
15+
16+
spot_ctrl_joint_infos = {
17+
"fl.hy": ControlJointValue( 10000.0, 0.01, 1.0, 14.0, 0.91, 0.0),
18+
"fl.kn": ControlJointValue( 10000.0, 0.01, 1.0, 14.0, -1.52, 0.0),
19+
"fr.hy": ControlJointValue( 10000.0, 0.01, 1.0, 14.0, 0.91, 0.0),
20+
"fr.kn": ControlJointValue( 10000.0, 0.01, 1.0, 14.0, -1.52, 0.0),
21+
"hl.hy": ControlJointValue( 10000.0, 0.01, 1.0, 14.0, 0.91, 0.0),
22+
"hl.kn": ControlJointValue( 10000.0, 0.01, 1.0, 14.0, -1.52, 0.0),
23+
"hr.hy": ControlJointValue( 10000.0, 0.01, 1.0, 14.0, 0.91, 0.0),
24+
"hr.kn": ControlJointValue( 10000.0, 0.01, 1.0, 14.0, -1.52, 0.0),
25+
}
26+
27+
def toDict(self):
28+
return dataclasses.asdict(self)
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
import Sofa
2+
from compliancerobotics.robots.spot import SpotRobot
3+
4+
from splib.simulation.linear_solvers import addLinearSolver
5+
from splib.simulation.ode_solvers import addImplicitODE
6+
from splib.simulation.headers import setupDefaultHeader
7+
8+
from stlib.core.basePrefab import BasePrefab
9+
from splib3 import animation
10+
from splib3.numerics.quat import Quat
11+
import numpy
12+
import math
13+
def setupScene(self, **kwargs):
14+
self.addObject('VisualStyle', name="visual_style")
15+
self.gravity = [0, 0, -9.81]
16+
self.dt = 0.01
17+
18+
self.add("BackgroundSetting", name="background_setting", color=[0.0,0.0,0.0,1.0])
19+
self.add("RequiredPlugin", name="external_plugins", pluginName=['Sofa.Component.Constraint.Projective',
20+
'Sofa.Component.Engine.Select',
21+
'Sofa.Component.LinearSolver.Direct',
22+
'Sofa.Component.Mass',
23+
'Sofa.Component.ODESolver.Backward',
24+
'Sofa.Component.SolidMechanics.FEM.Elastic',
25+
'Sofa.Component.StateContainer',
26+
'Sofa.Component.Topology.Container.Grid',
27+
'Sofa.Component.IO.Mesh',
28+
'Sofa.Component.LinearSolver.Direct',
29+
'Sofa.Component.ODESolver.Forward',
30+
'Sofa.Component.Topology.Container.Dynamic',
31+
'Sofa.Component.Visual',
32+
'Sofa.Component.Constraint.Lagrangian.Solver'
33+
])
34+
35+
self.addObject("FreeMotionAnimationLoop")
36+
self.addObject('ProjectedGaussSeidelConstraintSolver', name='constraint_solver',
37+
tolerance=1e-8, maxIterations=500, multithreading=True)
38+
self.addChild('modelling')
39+
self.addChild('simulation')
40+
return self
41+
42+
def Simulation(name="Simulation", **kwargs):
43+
def ImplicitODE(parent):
44+
return addImplicitODE(parent)
45+
46+
def LinearSolver(parent):
47+
return addLinearSolver(parent)
48+
49+
self = Sofa.Core.Node(**({"name":name} | kwargs))
50+
self.apply(addImplicitODE) # or add or mutate or ?
51+
self.apply(addLinearSolver)
52+
return self
53+
54+
def setupSimulation(self, **kwargs):
55+
self.apply(addImplicitODE, **kwargs)
56+
self.apply(addLinearSolver, **kwargs)
57+
self.add('GenericConstraintCorrection', name="constraint_correction", linearSolver=self.LinearSolver.linkpath)
58+
return self
59+
60+
class SpotWithGS(SpotRobot):
61+
@staticmethod
62+
def get_asset(name) -> str:
63+
import os
64+
return os.path.join(os.path.join(os.path.dirname(__file__), "assets"), name)
65+
66+
def __init__(self, *args, **kwargs):
67+
SpotRobot.__init__(self,*args,**kwargs)
68+
self.setup_visual()
69+
self.create_visual()
70+
71+
def setup_visual(self):
72+
self.settings.add("RequiredPlugin", name="Sofa.PointCloud")
73+
74+
def create_visual(self):
75+
child = self.add(Sofa.Core.Node, name="visual")
76+
g=child.add("PointCloudContainer", name="loader", filename=SpotWithGS.get_asset("spot-cleaned.ply"))
77+
g=child.add("PointCloudContainer", name="geometry", filename=SpotWithGS.get_asset("spot-cleaned.ply"))
78+
g=child.add("PointCloudTransform", name="transform", input=child.loader.linkpath, output=child.geometry.linkpath,
79+
scale = [1.5,1.0,1.0])
80+
81+
from stlib.core.baseParameters import BaseParameters, dataclasses
82+
class CameraParameters(BaseParameters):
83+
pass
84+
85+
class Camera(BasePrefab):
86+
def __init__(self, parameters : CameraParameters = None, **kwargs):
87+
if parameters is None:
88+
parameters = CameraParameters(**kwargs)
89+
BasePrefab.__init__(self, parameters)
90+
self.create_prefab()
91+
92+
def create_prefab(self):
93+
self.add("InteractiveCamera", name="state", computeZClip=True, zFar=10000)
94+
95+
class EulerToQuaternion(Sofa.Core.Controller):
96+
def __init__(self, *args, **kwargs):
97+
Sofa.Core.Controller.__init__(self,*args, **kwargs)
98+
self.addData(name="euler_angle", value=[0.0,0.0,0.0], default=[0.0,0.0,0.0], help="", group="Input", type="Vec3d")
99+
self.addData(name="quaternion", value=[0.0,0.0,0.0,1.0], default=[0.0,0.0,0.0,1.0], help="", group="Output", type="Quat")
100+
101+
def onIdleEvent(self, event):
102+
self.quaternion = list(Quat.createFromEuler(self.euler_angle.value))
103+
104+
class RigidDofBuilder(Sofa.Core.Controller):
105+
def __init__(self, *args, **kwargs):
106+
Sofa.Core.Controller.__init__(self,*args, **kwargs)
107+
self.addData(name="translation", value=[0.0,0.0,0.0], default=[0.0,0.0,0.0], help="", group="Input", type="Vec3d")
108+
self.addData(name="orientation", value=[0.0,0.0,0.0,1.0], default=[0.0,0.0,0.0,1.0], help="", group="Input", type="Quat")
109+
self.addData(name="rigid", value=[0.0,0.0,0.0,0.0,0.0,0.0,1.0], default=[0.0,0.0,0.0,0.0,0.0,0.0,1.0], help="", group="Output", type="Rigid3::Coord")
110+
111+
def onIdleEvent(self, event):
112+
self.rigid = self.translation.value.tolist() + self.orientation.value.tolist()
113+
114+
import Sofa
115+
import json
116+
117+
def set(base : Sofa.Core.Base, **kwargs):
118+
for k,v in kwargs.items():
119+
d = base.getData(k)
120+
if d is not None:
121+
d.value = v
122+
123+
def set_if(root, cond, **kwargs):
124+
for object in root.objects:
125+
if cond(object):
126+
set(object, **kwargs)
127+
128+
for child in root.children:
129+
if cond(child):
130+
set(child, **kwargs)
131+
set_if(child, cond, **kwargs)
132+
133+
class IdentitySkinning(Sofa.Core.Controller):
134+
def __init__(self, *args, **kwargs):
135+
Sofa.Core.Controller.__init__(self, *args, **kwargs)
136+
self.addData("indices", value=[0], default=[0], help="size", group="", type="vector<int>")
137+
self.container = kwargs.get("container")
138+
self.renderer = kwargs.get("renderer")
139+
self.target = kwargs.get("target")
140+
self.model = kwargs.get("model")
141+
142+
self.vtx_group = dict(json.load(open(SpotRobot.get_asset("spot-vertex-group.json"))))
143+
print(self.vtx_group.keys())
144+
145+
self.target.showObject=True
146+
self.names = {
147+
"universe" : "Body",
148+
"fl.hy" : "LegLeftFrontUp",
149+
"fl.kn" : "LegLeftFrontDown",
150+
"fr.hy" : "LegRightFrontUp",
151+
"fr.kn" : "LegRightFrontDown",
152+
"hl.hy" : "LegLeftBackUp",
153+
"hl.kn" : "LegLeftBackDown",
154+
"hr.hy" : "LegRightBackUp",
155+
"hr.kn" : "LegRightBackDown",
156+
}
157+
self.names2idx = {
158+
"Body" : 0,
159+
"LegLeftFrontUp" : 1,
160+
"LegLeftFrontDown": 2,
161+
"LegRightFrontUp": 3,
162+
"LegRightFrontDown": 4,
163+
"LegLeftBackUp": 5,
164+
"LegLeftBackDown": 6,
165+
"LegRightBackUp": 7,
166+
"LegRightBackDown": 8
167+
}
168+
self.references = {
169+
"universe" : [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]],
170+
"fl.hy" : [[ 2.97850000e-01, 1.65945000e-01, -1.38771850e-11, -5.61779914e-11, 4.39462314e-01, -2.74843384e-11, 8.98261028e-01]],
171+
"fl.kn" : [[ 6.01576952e-02, 1.65945000e-01, -2.16443106e-01, -5.96543813e-11, -3.00293175e-01, 1.87805848e-11, 9.53846953e-01]],
172+
"fr.hy" : [[ 2.97850000e-01, -1.65945000e-01, -1.38771850e-11, 5.61779914e-11, 4.39462314e-01, 2.74843384e-11, 8.98261028e-01]],
173+
"fr.kn" : [[ 6.01576952e-02, -1.65945000e-01, -2.16443106e-01, 5.96543813e-11, -3.00293175e-01, -1.87805848e-11, 9.53846953e-01]],
174+
"hl.hy" : [[-2.97850000e-01, 1.65945000e-01, -1.38771852e-11, -5.61779920e-11, 4.39462314e-01, -2.74843387e-11, 8.98261028e-01]],
175+
"hl.kn" : [[-5.35542305e-01, 1.65945000e-01, -2.16443106e-01, -5.96543819e-11, -3.00293175e-01, 1.87805850e-11, 9.53846953e-01]],
176+
"hr.hy" : [[-2.97850000e-01, -1.65945000e-01, -1.38771852e-11, 5.61779920e-11, 4.39462314e-01, 2.74843387e-11, 8.98261028e-01]],
177+
"hr.kn" : [[-5.35542305e-01, -1.65945000e-01, -2.16443106e-01, 5.96543819e-11, -3.00293175e-01, -1.87805850e-11, 9.53846953e-01]]
178+
}
179+
self.initializeFrames()
180+
181+
def initializeFrames(self):
182+
splats_count = len(self.container.positions.value)
183+
frames_count = len(self.vtx_group.keys())
184+
indices = [0]*splats_count
185+
186+
for k,v in self.vtx_group.items():
187+
frame_id = self.names2idx[k]
188+
for i in v:
189+
indices[i] = frame_id
190+
191+
self.renderer.frameIndices = indices
192+
self.target.position = [[0,0,0,0,0,0,1]]*frames_count
193+
194+
with self.target.position.writeableArray() as w:
195+
i = 0
196+
for k,v in self.references.items():
197+
w[i:,:] = v[0]
198+
i+=1
199+
200+
def onAnimateEndEvent(self,event):
201+
with self.target.position.writeableArray() as w:
202+
i = 0
203+
for k,v in self.references.items():
204+
w[i:,:] = self.model.model.Frames.getChild(k).dof.position.value[0]
205+
i+=1
206+
207+
class SpotController(Sofa.Core.Controller):
208+
def __init__(self, *args, **kwargs):
209+
Sofa.Core.Controller.__init__(self,*args,**kwargs)
210+
self.target = kwargs.get("target")
211+
212+
def onKeypressedEvent(self, event):
213+
def myAnimation(factor, target):
214+
self.target.model.getObject("jointfl.hy").value = 0.2*factor*3
215+
self.target.model.getObject("jointfl.kn").value = 0.5*math.sin(factor*2.0*3.14/2)
216+
217+
self.target.model.getObject("jointfr.hy").value = 2.0*math.cos(factor*2.0*3.14)
218+
self.target.model.getObject("jointfr.kn").value = 2.0*math.sin(3+factor*1.5*3.14)-1.0
219+
220+
self.target.model.getObject("jointhl.hy").value = 0.2*factor*1.5+0.3
221+
self.target.model.getObject("jointhl.kn").value = math.sin(factor*2.0*3.14/2)
222+
223+
self.target.model.getObject("jointhr.hy").value = 0.4*factor*3
224+
self.target.model.getObject("jointhr.kn").value = 1.5*math.sin(factor*2.0*3.14/2)-1.0
225+
226+
if event["key"] == 'P':
227+
set_if(self.target.model, lambda x: isinstance(x, Sofa.Core.Node) and x.name.value == "Visual",
228+
activated = False)
229+
else:
230+
animation.animate(myAnimation, {"target":self}, duration=1.0, mode="pingpong")
231+
232+
def setupAnimation(parent):
233+
return parent.addObject(animation.AnimationManager(parent))
234+
235+
def createScene(root):
236+
import SofaRuntime
237+
SofaRuntime.DataRepository.addLastPath("/home/dmarchal/projects/dev/sofa1/plugins/Sofa.PointCloud/examples/assets")
238+
239+
root.apply(setupScene)
240+
root.apply(setupAnimation)
241+
242+
root.add(Camera(name="camera"))
243+
244+
spot = Sofa.Core.Node("spot2")
245+
spot.add(SpotWithGS(name="model"))
246+
spot.add(SpotController, name="controller", target=spot.model)
247+
spot.model.visual.init()
248+
root.modelling.add(spot)
249+
250+
root.add(EulerToQuaternion, name="eulertoquat")
251+
root.add(RigidDofBuilder, name="rigidbuilder")
252+
root.rigidbuilder.orientation.setParent(root.eulertoquat.quaternion.linkpath)
253+
254+
spot.model.visual.transform.frame = [0.05, -0.145, 0.305, 0.011913, -0.0587687, 0.198312, 0.978303]
255+
spot.model.visual.transform.scale = [1.20, 1, 1]
256+
257+
#spot.model.visual.transform.frame.setParent(root.rigidbuilder.rigid.linkpath)
258+
259+
root.add("MechanicalObject", name="renderer_frames", template="Rigid3")
260+
root.add("PointCloudRenderer", name="renderer",
261+
geometry=spot.model.visual.geometry.linkpath,
262+
camera=root.camera.state.linkpath,
263+
frames=root.renderer_frames.position.linkpath
264+
)
265+
266+
root.add(IdentitySkinning, name="skin", model=spot.model,
267+
target=root.renderer_frames,
268+
renderer=root.renderer,
269+
container=spot.model.visual.geometry)
270+
271+
root.simulation.apply(setupSimulation)
272+
root.simulation.add(root.modelling)

0 commit comments

Comments
 (0)