9 Commits

Author SHA1 Message Date
c6458248a7 collision detection ik 2026-07-15 16:32:18 +08:00
4add432f53 add collision detection 2026-07-13 15:05:34 +01:00
58e84c6a33 add collision detection 2026-07-13 14:54:33 +01:00
7050c93c84 Upload files to "kine_ctrl/workspace_comfortable"
add
2026-07-13 18:57:58 +08:00
6db8b2f254 Upload files to "kine_ctrl/workspace_comfortable"
add
2026-07-13 18:57:44 +08:00
a5fb40d1a6 Upload files to "kine_ctrl/workspace_comfortable"
results
2026-07-13 18:57:11 +08:00
223b29f37d Upload files to "kine_ctrl/workspace_comfortable"
ik success rate. no self-collision detection used. no tool installed
2026-07-13 18:56:17 +08:00
cb51ecf2eb test_120Ori+0.05m 2026-07-13 16:27:35 +08:00
75ba51c609 after calculation 2026-07-10 16:55:08 +08:00
29 changed files with 32062 additions and 8 deletions

View File

@ -71,7 +71,7 @@ def main():
solve_sum = 0 solve_sum = 0
for i in range(1000): for i in range(10):
print(f'\n-------------- in i = {i} ----------------') print(f'\n-------------- in i = {i} ----------------')
joint_rand = np.random.uniform(ub, lb) joint_rand = np.random.uniform(ub, lb)
print(f'the predefined joints are {joint_rand}') print(f'the predefined joints are {joint_rand}')
@ -96,6 +96,7 @@ def main():
fk_qp_p2 = robot_kine_qp.forward_kinematics(q, tool=tool_name) fk_qp_p2 = robot_kine_qp.forward_kinematics(q, tool=tool_name)
d_p_ik = cal_pose_deviation(pose1=t_p, pose2=fk_qp_p2) d_p_ik = cal_pose_deviation(pose1=t_p, pose2=fk_qp_p2)
print(f'---- success, in the qp ik, fk_qp_p2 = {fk_qp_p2}, d_p_ik = {d_p_ik}') print(f'---- success, in the qp ik, fk_qp_p2 = {fk_qp_p2}, d_p_ik = {d_p_ik}')
# robot_kine_qp.collision_detect(q,stop_at_first_collision=True, verbose=True)
if d_p_ik < 0.01: if d_p_ik < 0.01:
result[0][1] += 1 result[0][1] += 1

3
kine_ctrl/req.txt Normal file
View File

@ -0,0 +1,3 @@
conda install -c conda-forge osqp scipy tqdm matplotlib pandas "numpy<1.24" pinocchio -y
pip install urdfpy mujoco "networkx>=2.8.4"
pip install Robotic_Arm

View File

@ -20,8 +20,12 @@ class KinematicsSolver():
unit: m, rad unit: m, rad
""" """
print(f' ------------ the qp based kinematic initialising -----------') print(f' ------------ the qp based kinematic initialising -----------')
self.model, collision_model, visual_model = pin.buildModelsFromUrdf(urdf_path, mesh_dir) self.model, self.collision_model, visual_model = pin.buildModelsFromUrdf(urdf_path, mesh_dir)
self.geom_model = pin.buildGeomFromUrdf(self.model, urdf_path, pin.GeometryType.COLLISION, mesh_dir)
self.geom_model.addAllCollisionPairs()
self.remove_adjacent_collision_pairs(verbose=True)
self.geom_data = pin.GeometryData(self.geom_model)
self.cfg_j_limit() self.cfg_j_limit()
@ -335,12 +339,100 @@ class KinematicsSolver():
iter_count += 1 iter_count += 1
if best_solution is not None: if best_solution is not None:
# return best_solution, True, best_error, iter_count collision = self.collision_detect(q=best_solution, stop_at_first_collision=True)
return 0, best_solution.tolist()
if collision is False:
# return best_solution, True, best_error, iter_count
return 0, best_solution.tolist()
else:
return -2, q[:7].copy().tolist()
else: else:
# return q[:7].copy(), False, error_norm, iter_count # return q[:7].copy(), False, error_norm, iter_count
return -1, q[:7].copy().tolist() return -1, q[:7].copy().tolist()
def collision_detect(self, q ,stop_at_first_collision=True, verbose=False ):
q = np.asarray(q, dtype=np.float64).reshape(-1)
if q.shape[0] != self.model.nq:
raise ValueError(f"q size mismatch: expected {self.model.nq}, got {q.shape[0]}")
# Update robot kinematics
pin.forwardKinematics(self.model, self.data, q)
pin.updateGeometryPlacements(
self.model,
self.data,
self.geom_model,
self.geom_data,
q
)
# Now compute collisions on the updated geometry model
collision = pin.computeCollisions(
self.geom_model,
self.geom_data,
stop_at_first_collision
)
if verbose:
print(f"the collision is {collision}\n")
for k, cr in enumerate(self.geom_data.collisionResults):
if cr.isCollision():
cp = self.geom_model.collisionPairs[k]
geom1 = self.geom_model.geometryObjects[cp.first]
geom2 = self.geom_model.geometryObjects[cp.second]
print(
f"collision pair {k}: "
f"{geom1.name} <--> {geom2.name}"
)
return bool(collision)
def remove_adjacent_collision_pairs(self, verbose=True):
"""
Remove collision pairs between same/adjacent parent joints.
This avoids false positives such as:
base_link_0 <--> link_1_0
"""
pairs_to_remove = []
for pair_id, pair in enumerate(self.geom_model.collisionPairs):
geom1 = self.geom_model.geometryObjects[pair.first]
geom2 = self.geom_model.geometryObjects[pair.second]
j1 = geom1.parentJoint
j2 = geom2.parentJoint
# Same body or directly connected bodies
if j1 == j2 or abs(j1 - j2) <= 1:
pairs_to_remove.append(pair_id)
if verbose:
print(
"Removing adjacent pair:",
pair_id,
geom1.name,
"<-->",
geom2.name,
"parentJoint:",
j1,
j2,
)
for pair_id in reversed(pairs_to_remove):
self.geom_model.removeCollisionPair(
self.geom_model.collisionPairs[pair_id]
)
# Important: recreate geometry data after modifying pairs
self.geom_data = pin.GeometryData(self.geom_model)
if verbose:
print("Remaining collision pairs:", len(self.geom_model.collisionPairs))
def quaternion_to_euler(self, q): def quaternion_to_euler(self, q):
""" """
Convert quaternion to Euler angles (roll, pitch, yaw) Convert quaternion to Euler angles (roll, pitch, yaw)

View File

@ -0,0 +1,80 @@
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# --------------------------------------------------
# 1. Load the data
# --------------------------------------------------
csv_path = Path("workspace_nocollisiondetection.csv")
# The file has no column names, so header=None is important.
df = pd.read_csv(
csv_path,
header=None,
names=["x", "y", "z", "ik_rate"],
)
print(df.head())
print("z planes:", np.sort(df["z"].unique()))
# --------------------------------------------------
# 2. Create an output directory
# --------------------------------------------------
output_dir = Path("contour_plots")
output_dir.mkdir(exist_ok=True)
# --------------------------------------------------
# 3. Use the same colour scale for every z-plane
# --------------------------------------------------
value_min = df["ik_rate"].min()
value_max = df["ik_rate"].max()
# More levels give a smoother-looking contour plot.
levels = np.linspace(value_min, value_max, 51)
# --------------------------------------------------
# 4. Draw one contour plot for each z-plane
# --------------------------------------------------
for z_value, plane in df.groupby("z", sort=True):
# Rows become y-coordinates, columns become x-coordinates.
grid = plane.pivot(index="y", columns="x", values="ik_rate")
x = grid.columns.to_numpy()
y = grid.index.to_numpy()
ik_grid = grid.to_numpy()
X, Y = np.meshgrid(x, y)
fig, ax = plt.subplots(figsize=(7, 6))
contour = ax.contourf(
X,
Y,
ik_grid,
levels=levels,
cmap="viridis",
extend="both",
)
colorbar = fig.colorbar(contour, ax=ax)
colorbar.set_label("IK rate")
ax.set_title(f"IK rate at z = {z_value:.2f}")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_aspect("equal")
fig.tight_layout()
output_path = output_dir / f"ik_contour_z_{z_value:.2f}.png"
fig.savefig(output_path, dpi=200, bbox_inches="tight")
plt.close(fig)
print(f"Plots saved to: {output_dir.resolve()}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,9 @@ X_RANGE = (-0.6, 0.6)
Y_RANGE = (-0.6, 0.6) Y_RANGE = (-0.6, 0.6)
Z_RANGE = (0.0, 0.8) Z_RANGE = (0.0, 0.8)
GRID_RESOLUTION = 0.3 # 5 cm. Use 0.02 for finer but slower. GRID_RESOLUTION = 0.05 # 5 cm. Use 0.02 for finer but slower.
num_orientations = 120
# Comfort thresholds # Comfort thresholds
MIN_JOINT_MARGIN = 0.05 # 15% away from joint limits MIN_JOINT_MARGIN = 0.05 # 15% away from joint limits
@ -133,7 +135,7 @@ JACOBIAN_EPS = 1e-5
# 2. TASK ORIENTATION SAMPLING # 2. TASK ORIENTATION SAMPLING
# ============================================================ # ============================================================
def make_task_orientations(num_orientations=200, seed=1): def make_task_orientations(num_orientations=num_orientations, seed=1):
""" """
Random orientation sampling using RM's Euler convention: Random orientation sampling using RM's Euler convention:
@ -556,8 +558,7 @@ def evaluate_workspace():
for rpy in orientations: for rpy in orientations:
attempted += 1 attempted += 1
# print(f"\n - target point: {point}, target orientation: {rpy}")
rpy = [1.2022060487764064, -1.0097962261845583, -0.6518417572686532]
ik_result = solve_ik_user(point, rpy) ik_result = solve_ik_user(point, rpy)
# print(f'\n point is {point}, rpy is {rpy}, and ik result q: {ik_result}') # print(f'\n point is {point}, rpy is {rpy}, and ik result q: {ik_result}')