Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
4 changes: 3 additions & 1 deletion embodichain/lab/scripts/run_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ def generate_function(

def main(args, env, gym_config):
if getattr(args, "preview", False):
log_warning("Preview mode enabled. Launching environment preview...")
log_info(
"Preview mode enabled. Launching environment preview...", color="green"
)
preview(env)

log_info("Start offline data generation.", color="green")
Expand Down
1 change: 1 addition & 0 deletions embodichain/lab/sim/utility/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
from .sim_utils import *
from .mesh_utils import *
from .gizmo_utils import *
from .keyboard_utils import *
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

Import pollutes the enclosing namespace, as the imported module embodichain.lab.sim.utility.keyboard_utils does not define 'all'.

Suggested change
from .keyboard_utils import *
from . import keyboard_utils

Copilot uses AI. Check for mistakes.
186 changes: 186 additions & 0 deletions embodichain/lab/sim/utility/keyboard_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# ----------------------------------------------------------------------------
# Copyright (c) 2021-2025 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------

import cv2
import torch

from scipy.spatial.transform import Rotation as R

from embodichain.lab.sim.sensors import Camera
from embodichain.utils.logger import log_info, log_error, log_warning


def run_keyboard_control_for_camera(
sensor: Camera, trans_step: float = 0.01, rot_step: float = 1.0
) -> None:
if sensor.num_instances > 1:
log_warning(
"Multiple sensor instances detected. Keyboard control will only work for one instance."
)
return

log_info("\n=== Camera Pose Control ===")
log_info("Translation controls:")
log_info(" W/S: Move forward/backward (Z-axis)")
log_info(" A/D: Move left/left (Y-axis)")
log_info(" Q/E: Move up/down (X-axis)")
log_info("\nRotation controls:")
log_info(" I/K: Pitch up/down (X-rotation)")
log_info(" J/L: Yaw left/left (Z-rotation)")
log_info(" U/O: Roll left/left (Y-rotation)")
Comment thread
yuecideng marked this conversation as resolved.
log_info("\nOther controls:")
log_info(" R: Reset to initial pose")
log_info(" P: Print current pose")
log_info(" ESC: Exit control mode")

init_pose = sensor.get_local_pose(to_matrix=True).squeeze().numpy()

try:
while True:
current_pose = sensor.get_local_pose(to_matrix=True).squeeze().numpy()
Comment thread
yuecideng marked this conversation as resolved.

sensor.update()
image = sensor.get_data()["color"].squeeze(0).cpu().numpy()
Comment thread
yuecideng marked this conversation as resolved.
image_vis = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
cv2.putText(
image_vis,
"Press keys to control camera pose",
(10, 30),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
(0, 255, 0),
2,
)
cv2.putText(
image_vis,
"ESC to exit control mode",
(10, 60),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
(0, 255, 0),
2,
)
cv2.imshow("cam view", image_vis)
key = cv2.waitKey(1) & 0xFF

if key == 255:
continue
elif key == 27:
log_info("Exiting keyboard control mode...")
break

pose_changed = False
new_pose = current_pose.copy()

# controlling translation
if key == ord("w"):
new_pose[2, 3] += trans_step
pose_changed = True
log_info(f"Moving forward: Z += {trans_step}")
elif key == ord("s"):
new_pose[2, 3] -= trans_step
pose_changed = True
log_info(f"Moving backward: Z -= {trans_step}")
elif key == ord("a"):
new_pose[1, 3] -= trans_step
pose_changed = True
log_info(f"Moving left: Y -= {trans_step}")
elif key == ord("d"):
new_pose[1, 3] += trans_step
pose_changed = True
log_info(f"Moving left: Y += {trans_step}")
elif key == ord("q"):
Comment thread
yuecideng marked this conversation as resolved.
new_pose[0, 3] += trans_step
pose_changed = True
log_info(f"Moving up: X += {trans_step}")
elif key == ord("e"):
new_pose[0, 3] -= trans_step
pose_changed = True
log_info(f"Moving down: X -= {trans_step}")
Comment thread
yuecideng marked this conversation as resolved.

# controlling rotation
elif key == ord("i"):
current_rotation = R.from_matrix(new_pose[:3, :3])
delta_rotation = R.from_euler("x", rot_step, degrees=True)
new_rotation = delta_rotation * current_rotation
new_pose[:3, :3] = new_rotation.as_matrix()
pose_changed = True
log_info(f"Pitch up: X rotation += {rot_step}°")
elif key == ord("k"):
current_rotation = R.from_matrix(new_pose[:3, :3])
delta_rotation = R.from_euler("x", -rot_step, degrees=True)
new_rotation = delta_rotation * current_rotation
new_pose[:3, :3] = new_rotation.as_matrix()
pose_changed = True
log_info(f"Pitch down: X rotation -= {rot_step}°")
elif key == ord("j"):
current_rotation = R.from_matrix(new_pose[:3, :3])
delta_rotation = R.from_euler("z", rot_step, degrees=True)
new_rotation = delta_rotation * current_rotation
new_pose[:3, :3] = new_rotation.as_matrix()
pose_changed = True
log_info(f"Yaw left: Z rotation += {rot_step}°")
elif key == ord("l"):
current_rotation = R.from_matrix(new_pose[:3, :3])
delta_rotation = R.from_euler("z", -rot_step, degrees=True)
new_rotation = delta_rotation * current_rotation
new_pose[:3, :3] = new_rotation.as_matrix()
pose_changed = True
log_info(f"Yaw left: Z rotation -= {rot_step}°")
Comment thread
yuecideng marked this conversation as resolved.
elif key == ord("u"):
current_rotation = R.from_matrix(new_pose[:3, :3])
delta_rotation = R.from_euler("y", rot_step, degrees=True)
new_rotation = delta_rotation * current_rotation
new_pose[:3, :3] = new_rotation.as_matrix()
pose_changed = True
log_info(f"Roll left: Y rotation += {rot_step}°")
elif key == ord("o"):
current_rotation = R.from_matrix(new_pose[:3, :3])
delta_rotation = R.from_euler("y", -rot_step, degrees=True)
new_rotation = delta_rotation * current_rotation
new_pose[:3, :3] = new_rotation.as_matrix()
pose_changed = True
log_info(f"Roll left: Y rotation -= {rot_step}°")

# other controls
elif key == ord("r"):
new_pose = init_pose.copy()
pose_changed = True
log_info("Reset to initial pose")
elif key == ord("p"):
translation = new_pose[:3, 3]
rot = R.from_matrix(new_pose[:3, :3])
quaternion = rot.as_quat()
log_info("Current Camera pose:")
log_info(f"Translation: {translation}")
quat_wxyz = [quaternion[3], quaternion[0], quaternion[1], quaternion[2]]
log_info(f"Quaternion (w, x, y, z): {quat_wxyz}")

rotation_euler = rot.as_euler("xyz", degrees=True)
log_info(f"Rotation (XYZ Euler, degrees): {rotation_euler}")

if pose_changed:
cam_pose = new_pose.copy()
cam_pose = torch.as_tensor(cam_pose, dtype=torch.float32).unsqueeze_(0)
sensor.set_local_pose(cam_pose)

except KeyboardInterrupt:
log_error("Keyboard control interrupted by user. Exiting control mode...")
Comment thread
yuecideng marked this conversation as resolved.
finally:
try:
cv2.destroyAllWindows()
except Exception as e:
log_warning(f"cv2.destroyAllWindows() failed: {e}")
Loading