Skip to content

Commit d07dbda

Browse files
yhnsuyuanhaonan
andauthored
add solver and motion generator docs (#31)
Co-authored-by: yuanhaonan <[email protected]>
1 parent 5214384 commit d07dbda

File tree

5 files changed

+264
-28
lines changed

5 files changed

+264
-28
lines changed

docs/source/tutorial/motion_gen.rst

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ Motion Generator
66

77
.. currentmodule:: embodichain.lab.sim.planners.motion_generator
88

9-
Overview
10-
~~~~~~~~
11-
129
The ``MotionGenerator`` class in EmbodiChain provides a unified and extensible interface for robot trajectory planning. It supports time-optimal trajectory generation (currently via TOPPRA), joint/Cartesian interpolation, and is designed for easy integration with RL, imitation learning, and classical control scenarios.
1310

1411
Key Features
@@ -20,6 +17,19 @@ Key Features
2017
- **Extensible**: Easy to add new planners (RRT, PRM, etc.)
2118
- **Integration Ready**: Can be used in RL, imitation learning, or classical pipelines
2219

20+
21+
The Code
22+
~~~~~~~~
23+
24+
The tutorial corresponds to the ``motion_generator.py`` script in the ``scripts/tutorials/sim`` directory.
25+
26+
.. dropdown:: Code for motion_generator.py
27+
:icon: code
28+
29+
.. literalinclude:: ../../../scripts/tutorials/sim/motion_generator.py
30+
:language: python
31+
:linenos:
32+
2333
Typical Usage
2434
~~~~~~~~~~~~~
2535

docs/source/tutorial/solver.rst

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,26 @@ Key Features
1919
- **Batch and Single Query**: Supports both batch and single FK/IK/Jacobian queries.
2020
- **Extensible**: New solvers can be added by subclassing `BaseSolver` and implementing required methods.
2121

22-
Example: Using PinkSolver
23-
~~~~~~~~~~~~~~~~~~~~~~~~~
22+
The Code
23+
~~~~~~~~
2424

25+
The tutorial corresponds to the ``srs_solver.py`` script in the ``scripts/tutorials/sim`` directory.
2526

26-
.. code-block:: python
27+
.. dropdown:: Code for srs_solver.py
28+
:icon: code
29+
30+
.. literalinclude:: ../../../scripts/tutorials/sim/srs_solver.py
31+
:language: python
32+
:linenos:
2733

28-
from embodichain.lab.sim.solvers import PinkSolverCfg
29-
from embodichain.lab.sim.objects.robot import Robot
34+
Typical Usage
35+
~~~~~~~~~~~~~~~~~~~~~~~~
3036

31-
# 1. Configure PinkSolver
32-
pink_cfg = PinkSolverCfg(
37+
**Step 1: Configure solver**
38+
39+
.. code-block:: python
40+
41+
srs_cfg = SrsSolverCfg(
3342
urdf_path="/path/to/robot.urdf",
3443
joint_names=[
3544
"shoulder_pan_joint", "shoulder_lift_joint", "elbow_joint",
@@ -38,29 +47,30 @@ Example: Using PinkSolver
3847
end_link_name="ee_link",
3948
root_link_name="base_link"
4049
)
41-
# 2. Assign solver config to robot config
42-
robot_cfg.solver_cfg = pink_cfg
43-
# 3. Instantiate robot (solver will be initialized automatically)
50+
51+
**Step 2: Instantiate the robot with solver**
52+
53+
.. code-block:: python
54+
55+
robot_cfg.solver_cfg = srs_cfg
4456
robot = Robot(cfg=robot_cfg, entities=[], device="cpu")
4557
46-
# 4. Use FK/IK/Jacobian
47-
qpos = [0.0, -1.57, 1.57, 0.0, 1.57, 0.0] # 6-DOF joint angles (radians)
48-
ee_pose = robot.compute_fk(qpos) # Forward kinematics, returns 4x4 matrix
49-
print("End-effector pose (FK):\n", ee_pose)
58+
**Step 3: Use FK/IK/Jacobian**
59+
60+
.. code-block:: python
61+
62+
qpos = [0.0, -1.57, 1.57, 0.0, 1.57, 0.0]
63+
ee_pose = robot.compute_fk(qpos)
5064
51-
import numpy as np
5265
target_pose = np.array([
5366
[0, -1, 0, 0.5],
5467
[1, 0, 0, 0.2],
5568
[0, 0, 1, 0.3],
5669
[0, 0, 0, 1.0]
5770
])
5871
success, qpos_sol = robot.compute_ik(target_pose, joint_seed=qpos)
59-
print("IK success:", success)
60-
print("IK solution:", qpos_sol)
61-
6272
J = robot.get_solver().get_jacobian(qpos)
63-
print("Jacobian:\n", J)
73+
6474
6575
**Note**
6676

@@ -88,12 +98,6 @@ API Reference
8898
- **set_position_limits / get_position_limits**: Set or get joint position limits.
8999
- **set_tcp / get_tcp**: Set or get the tool center point (TCP) transformation.
90100

91-
**PinkSolver**
92-
93-
- Implements all BaseSolver methods using the Pink library.
94-
- Supports custom task lists, solver type selection, and joint limit handling.
95-
- See PinkSolverCfg for all configuration options.
96-
97101
Configuration
98102
~~~~~~~~~~~~~
99103

examples/sim/planners/motion_generator.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# ----------------------------------------------------------------------------
2+
# Copyright (c) 2021-2025 DexForce Technology Co., Ltd.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ----------------------------------------------------------------------------
16+
117
import time
218
import torch
319
import numpy as np
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# ----------------------------------------------------------------------------
2+
# Copyright (c) 2021-2025 DexForce Technology Co., Ltd.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ----------------------------------------------------------------------------
16+
17+
import time
18+
import torch
19+
import numpy as np
20+
from copy import deepcopy
21+
from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
22+
from embodichain.lab.sim.objects import Robot
23+
from embodichain.lab.sim.robots import CobotMagicCfg
24+
from embodichain.lab.sim.planners.utils import TrajectorySampleMethod
25+
from embodichain.lab.sim.planners.motion_generator import MotionGenerator
26+
27+
28+
def move_robot_along_trajectory(
29+
robot: Robot, arm_name: str, qpos_list: list[torch.Tensor], delay: float = 0.1
30+
):
31+
"""
32+
Set the robot joint positions sequentially along the given joint trajectory.
33+
Args:
34+
robot: Robot instance.
35+
arm_name: Name of the robot arm.
36+
qpos_list: List of joint positions (torch.Tensor).
37+
delay: Time delay between each step (seconds).
38+
"""
39+
for q in qpos_list:
40+
robot.set_qpos(qpos=q.unsqueeze(0), joint_ids=robot.get_joint_ids(arm_name))
41+
time.sleep(delay)
42+
43+
44+
def create_demo_trajectory(
45+
robot: Robot, arm_name: str
46+
) -> tuple[list[torch.Tensor], list[np.ndarray]]:
47+
"""
48+
Generate a three-point trajectory (start, middle, end) for demonstration.
49+
Args:
50+
robot: Robot instance.
51+
arm_name: Name of the robot arm.
52+
Returns:
53+
qpos_list: List of joint positions (torch.Tensor).
54+
xpos_list: List of end-effector poses (numpy arrays).
55+
"""
56+
qpos_fk = torch.tensor(
57+
[[0.0, np.pi / 4, -np.pi / 4, 0.0, np.pi / 4, 0.0]], dtype=torch.float32
58+
)
59+
xpos_begin = robot.compute_fk(name=arm_name, qpos=qpos_fk, to_matrix=True)
60+
xpos_mid = deepcopy(xpos_begin)
61+
xpos_mid[0, 2, 3] -= 0.1 # Move down by 0.1m in Z direction
62+
xpos_final = deepcopy(xpos_mid)
63+
xpos_final[0, 0, 3] += 0.2 # Move forward by 0.2m in X direction
64+
65+
qpos_begin = robot.compute_ik(pose=xpos_begin, name=arm_name)[1][0]
66+
qpos_mid = robot.compute_ik(pose=xpos_mid, name=arm_name)[1][0]
67+
qpos_final = robot.compute_ik(pose=xpos_final, name=arm_name)[1][0]
68+
return [qpos_begin, qpos_mid, qpos_final], [
69+
xpos_begin[0],
70+
xpos_mid[0],
71+
xpos_final[0],
72+
]
73+
74+
75+
def main():
76+
np.set_printoptions(precision=5, suppress=True)
77+
torch.set_printoptions(precision=5, sci_mode=False)
78+
79+
# Initialize simulation
80+
sim = SimulationManager(SimulationManagerCfg(headless=False, sim_device="cpu"))
81+
sim.build_multiple_arenas(1)
82+
sim.set_manual_update(False)
83+
84+
# Robot configuration
85+
cfg_dict = {"uid": "CobotMagic"}
86+
robot: Robot = sim.add_robot(cfg=CobotMagicCfg.from_dict(cfg_dict))
87+
arm_name = "left_arm"
88+
89+
# # Generate trajectory points
90+
qpos_list, xpos_list = create_demo_trajectory(robot, arm_name)
91+
92+
# Initialize motion generator
93+
motion_generator = MotionGenerator(
94+
robot=robot,
95+
uid=arm_name,
96+
planner_type="toppra",
97+
default_velocity=0.2,
98+
default_acceleration=0.5,
99+
)
100+
101+
# Joint space trajectory
102+
out_qpos_list, _ = motion_generator.create_discrete_trajectory(
103+
qpos_list=[q.numpy() for q in qpos_list],
104+
is_linear=False,
105+
sample_method=TrajectorySampleMethod.QUANTITY,
106+
sample_num=20,
107+
)
108+
move_robot_along_trajectory(robot, arm_name, out_qpos_list)
109+
110+
# Cartesian space trajectory
111+
out_qpos_list, _ = motion_generator.create_discrete_trajectory(
112+
xpos_list=[x.numpy() for x in xpos_list],
113+
is_linear=True,
114+
sample_method=TrajectorySampleMethod.QUANTITY,
115+
sample_num=20,
116+
)
117+
move_robot_along_trajectory(robot, arm_name, out_qpos_list)
118+
119+
120+
if __name__ == "__main__":
121+
main()
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# ----------------------------------------------------------------------------
2+
# Copyright (c) 2021-2025 DexForce Technology Co., Ltd.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ----------------------------------------------------------------------------
16+
17+
import time
18+
import numpy as np
19+
import torch
20+
21+
from IPython import embed
22+
23+
from embodichain.lab.sim.objects import Robot
24+
from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
25+
from embodichain.lab.sim.robots import DexforceW1Cfg
26+
27+
28+
def main():
29+
# Set print options for better readability
30+
np.set_printoptions(precision=5, suppress=True)
31+
torch.set_printoptions(precision=5, sci_mode=False)
32+
33+
# Initialize simulation
34+
sim_device = "cpu"
35+
sim = SimulationManager(
36+
SimulationManagerCfg(
37+
headless=False, sim_device=sim_device, width=2200, height=1200
38+
)
39+
)
40+
41+
sim.build_multiple_arenas(1)
42+
sim.set_manual_update(False)
43+
44+
robot: Robot = sim.add_robot(cfg=DexforceW1Cfg.from_dict({"uid": "dexforce_w1"}))
45+
arm_name = "left_arm"
46+
# Set initial joint positions for left arm
47+
qpos_fk_list = [
48+
torch.tensor([[0.0, 0.0, 0.0, -np.pi / 2, 0.0, 0.0, 0.0]], dtype=torch.float32),
49+
]
50+
robot.set_qpos(qpos_fk_list[0], joint_ids=robot.get_joint_ids(arm_name))
51+
52+
time.sleep(0.5)
53+
54+
fk_xpos_batch = torch.cat(qpos_fk_list, dim=0)
55+
56+
fk_xpos_list = robot.compute_fk(qpos=fk_xpos_batch, name=arm_name, to_matrix=True)
57+
58+
start_time = time.time()
59+
res, ik_qpos = robot.compute_ik(
60+
pose=fk_xpos_list,
61+
name=arm_name,
62+
# joint_seed=qpos_fk_list[0],
63+
return_all_solutions=True,
64+
)
65+
end_time = time.time()
66+
print(
67+
f"Batch IK computation time for {len(fk_xpos_list)} poses: {end_time - start_time:.6f} seconds"
68+
)
69+
70+
if ik_qpos.dim() == 3:
71+
first_solutions = ik_qpos[:, 0, :]
72+
else:
73+
first_solutions = ik_qpos
74+
robot.set_qpos(first_solutions, joint_ids=robot.get_joint_ids(arm_name))
75+
76+
ik_xpos_list = robot.compute_fk(qpos=first_solutions, name=arm_name, to_matrix=True)
77+
78+
print("fk_xpos_list: ", fk_xpos_list)
79+
print("ik_xpos_list: ", ik_xpos_list)
80+
81+
embed(header="Test SRSSolver example. Press Ctrl-D to exit.")
82+
83+
84+
if __name__ == "__main__":
85+
main()

0 commit comments

Comments
 (0)