Skip to content

Commit 0d9b22c

Browse files
committed
wip
1 parent c600b48 commit 0d9b22c

File tree

3 files changed

+295
-6
lines changed

3 files changed

+295
-6
lines changed

embodichain/lab/sim/sim_manager.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,14 @@ def get_light(self, uid: str) -> Light | None:
803803
return None
804804
return self._lights[uid]
805805

806+
def get_light_uid_list(self) -> List[str]:
807+
"""Get current light uid list
808+
809+
Returns:
810+
List[str]: list of light uid.
811+
"""
812+
return list(self._lights.keys())
813+
806814
def add_rigid_object(
807815
self,
808816
cfg: RigidObjectCfg,

embodichain/lab/sim/utility/gizmo_utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ def gizmo_transform_callback(node, translation, rotation, flag):
5151

5252

5353
def run_gizmo_robot_control_loop(
54-
robot: "Robot", control_part: str = "arm", end_link_name: str | None = None
54+
robot: object | str, control_part: str = "arm", end_link_name: str | None = None
5555
):
5656
"""Run a control loop for testing gizmo controls on a robot.
5757
5858
This function implements a control loop that allows users to manipulate a robot
5959
using gizmo controls with keyboard input for additional commands.
6060
6161
Args:
62-
robot (Robot): The robot to control with the gizmo.
62+
robot (Robot | str): The robot to control with the gizmo.
6363
control_part (str, optional): The part of the robot to control. Defaults to "arm".
6464
end_link_name (str | None, optional): The name of the end link for FK calculations. Defaults to None.
6565
@@ -87,6 +87,9 @@ def run_gizmo_robot_control_loop(
8787

8888
sim = SimulationManager.get_instance()
8989

90+
if isinstance(robot, str):
91+
robot = sim.get_robot(uid=robot)
92+
9093
# Enter auto-update mode.
9194
sim.set_manual_update(False)
9295

embodichain/lab/sim/utility/keyboard_utils.py

Lines changed: 282 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
# limitations under the License.
1515
# ----------------------------------------------------------------------------
1616

17+
import select
18+
import sys
19+
import tty
20+
import termios
21+
import time
1722
import cv2
1823
import torch
1924
import numpy as np
@@ -25,19 +30,32 @@
2530

2631

2732
def run_keyboard_control_for_camera(
28-
sensor: Camera,
33+
sensor: Camera | str,
2934
trans_step: float = 0.01,
3035
rot_step: float = 1.0,
3136
vis_pose: bool = False,
3237
) -> None:
3338
"""Run keyboard control loop for camera pose adjustment.
3439
3540
Args:
36-
sensor (Camera): Camera sensor to control.
41+
sensor (Camera | str): Camera sensor or name of the camera to control.
3742
trans_step (float, optional): Translation step size. Defaults to 0.01.
3843
rot_step (float, optional): Rotation step size in degrees. Defaults to 1.0.
3944
vis_pose (bool, optional): Whether to visualize the camera pose in axis form. Defaults to False.
4045
"""
46+
from embodichain.lab.sim import SimulationManager
47+
48+
sim = SimulationManager.get_instance()
49+
50+
if vis_pose and sim.is_rt_enabled:
51+
log_warning(
52+
"'vis_pose' is not fully supported with ray tracing enabled. Will be fixed in future updates."
53+
)
54+
return
55+
56+
if isinstance(sensor, str):
57+
sensor = sim.get_sensor(uid=sensor)
58+
4159
if sensor.num_instances > 1:
4260
log_warning(
4361
"Multiple sensor instances detected. Keyboard control will only work for one instance."
@@ -62,12 +80,10 @@ def run_keyboard_control_for_camera(
6280

6381
marker = None
6482
if vis_pose:
65-
from embodichain.lab.sim import SimulationManager
6683
from embodichain.lab.sim.cfg import MarkerCfg
6784

6885
init_axis_pose = sensor.get_arena_pose(to_matrix=True).squeeze().numpy()
6986

70-
sim = SimulationManager.get_instance()
7187
marker = sim.draw_marker(
7288
cfg=MarkerCfg(
7389
name="camera_axis",
@@ -227,3 +243,265 @@ def run_keyboard_control_for_camera(
227243
cv2.destroyAllWindows()
228244
except Exception as e:
229245
log_warning(f"cv2.destroyAllWindows() failed: {e}")
246+
247+
248+
def run_keyboard_control_for_light(
249+
light: object | str,
250+
trans_step: float = 0.01,
251+
intensity_step: float = 1.0,
252+
falloff_step: float = 1.0,
253+
color_step: float = 0.05,
254+
vis_pose: bool = False,
255+
) -> None:
256+
"""Run keyboard control loop for light adjustment.
257+
258+
Args:
259+
light (Light | str): Light object or name of the light to control.
260+
trans_step (float, optional): Translation step size. Defaults to 0.01.
261+
intensity_step (float, optional): Intensity adjustment step. Defaults to 0.1.
262+
falloff_step (float, optional): Falloff/radius adjustment step. Defaults to 0.1.
263+
color_step (float, optional): Color channel adjustment step. Defaults to 0.05.
264+
vis_pose (bool, optional): Whether to visualize the light position with a marker. Defaults to False.
265+
"""
266+
from embodichain.lab.sim.objects import Light
267+
from embodichain.lab.sim import SimulationManager
268+
269+
sim = SimulationManager.get_instance()
270+
271+
if vis_pose and sim.is_rt_enabled:
272+
log_warning(
273+
"'vis_pose' is not fully supported with ray tracing enabled. Will be fixed in future updates."
274+
)
275+
return
276+
277+
if isinstance(light, str):
278+
light: Light = sim.get_light(uid=light)
279+
280+
if light.num_instances > 1:
281+
log_warning(
282+
"Multiple light instances detected. Keyboard control will only work for one instance."
283+
)
284+
return
285+
286+
log_info("\n=== Light Control ===")
287+
log_info("Translation controls:")
288+
log_info(" W/S: Move forward/backward (Z-axis)")
289+
log_info(" A/D: Move left/right (Y-axis)")
290+
log_info(" Q/E: Move up/down (X-axis)")
291+
log_info("\nIntensity controls:")
292+
log_info(" I/K: Increase/decrease intensity")
293+
log_info("\nFalloff controls:")
294+
log_info(" U/O: Increase/decrease falloff radius")
295+
log_info("\nColor controls:")
296+
log_info(" T/Y: Increase/decrease red channel")
297+
log_info(" G/H: Increase/decrease green channel")
298+
log_info(" B/N: Increase/decrease blue channel")
299+
log_info("\nOther controls:")
300+
log_info(" R: Reset to initial values")
301+
log_info(" P: Print current light properties")
302+
log_info(" ESC: Exit control mode")
303+
304+
# Store initial values from config
305+
init_pose = light.get_local_pose()
306+
init_color = torch.as_tensor(light.cfg.color).clone()
307+
init_intensity = float(light.cfg.intensity)
308+
init_falloff = float(light.cfg.radius)
309+
310+
# Current values
311+
current_color = init_color.clone()
312+
current_intensity = init_intensity
313+
current_falloff = init_falloff
314+
315+
marker = None
316+
if vis_pose:
317+
from embodichain.lab.sim import SimulationManager
318+
from embodichain.lab.sim.cfg import MarkerCfg
319+
320+
init_marker_pose = light.get_local_pose(to_matrix=True).squeeze().numpy()
321+
322+
sim = SimulationManager.get_instance()
323+
marker = sim.draw_marker(
324+
cfg=MarkerCfg(
325+
name="light_marker",
326+
marker_type="axis",
327+
axis_xpos=[init_marker_pose],
328+
axis_size=0.002,
329+
axis_len=0.05,
330+
)
331+
)
332+
333+
# TODO: We may add node to BatchEntity object.
334+
marker[0].node.attach_node(light._entities[0].get_node())
335+
336+
log_info("\nLight control active. Press keys to adjust light properties...")
337+
338+
# Save terminal settings
339+
old_settings = termios.tcgetattr(sys.stdin)
340+
tty.setcbreak(sys.stdin.fileno())
341+
342+
def get_key():
343+
"""Non-blocking keyboard input."""
344+
if select.select([sys.stdin], [], [], 0)[0]:
345+
return sys.stdin.read(1)
346+
return None
347+
348+
try:
349+
350+
while True:
351+
current_pose = light.get_local_pose().squeeze().numpy()
352+
353+
# Non-blocking key input
354+
key = get_key()
355+
356+
if key is None:
357+
continue
358+
elif key in ["\x1b"]: # Q or ESC
359+
if vis_pose:
360+
sim.remove_marker("light_marker")
361+
log_info("Exiting light control mode...")
362+
break
363+
364+
property_changed = False
365+
new_pose = current_pose.copy()
366+
367+
# Translation controls
368+
if key in ["w", "W"]:
369+
new_pose[2] += trans_step
370+
property_changed = True
371+
log_info(f"Moving forward: Z += {trans_step}")
372+
elif key in ["s", "S"]:
373+
new_pose[2] -= trans_step
374+
property_changed = True
375+
log_info(f"Moving backward: Z -= {trans_step}")
376+
elif key in ["a", "A"]:
377+
new_pose[1] -= trans_step
378+
property_changed = True
379+
log_info(f"Moving left: Y -= {trans_step}")
380+
elif key in ["d", "D"]:
381+
new_pose[1] += trans_step
382+
property_changed = True
383+
log_info(f"Moving right: Y += {trans_step}")
384+
elif key in ["q", "Q"]:
385+
new_pose[0] += trans_step
386+
property_changed = True
387+
log_info(f"Moving up: X += {trans_step}")
388+
elif key in ["e", "E"]:
389+
new_pose[0] -= trans_step
390+
property_changed = True
391+
log_info(f"Moving down: X -= {trans_step}")
392+
393+
# Intensity controls
394+
elif key in ["i", "I"]:
395+
current_intensity += intensity_step
396+
current_intensity = max(0.0, current_intensity)
397+
light.set_intensity(torch.tensor(current_intensity))
398+
property_changed = True
399+
log_info(f"Intensity increased to: {current_intensity:.2f}")
400+
elif key in ["k", "K"]:
401+
current_intensity -= intensity_step
402+
current_intensity = max(0.0, current_intensity)
403+
light.set_intensity(torch.tensor(current_intensity))
404+
property_changed = True
405+
log_info(f"Intensity decreased to: {current_intensity:.2f}")
406+
407+
# Falloff controls
408+
elif key in ["u", "U"]:
409+
current_falloff += falloff_step
410+
current_falloff = max(0.0, current_falloff)
411+
light.set_falloff(torch.tensor(current_falloff))
412+
property_changed = True
413+
log_info(f"Falloff increased to: {current_falloff:.2f}")
414+
elif key in ["o", "O"]:
415+
current_falloff -= falloff_step
416+
current_falloff = max(0.0, current_falloff)
417+
light.set_falloff(torch.tensor(current_falloff))
418+
property_changed = True
419+
log_info(f"Falloff decreased to: {current_falloff:.2f}")
420+
421+
# Color controls - Red channel
422+
elif key in ["t", "T"]:
423+
current_color[0] += color_step
424+
current_color[0] = torch.clamp(current_color[0], 0.0, 1.0)
425+
light.set_color(current_color)
426+
property_changed = True
427+
log_info(f"Red channel increased to: {current_color[0]:.2f}")
428+
elif key in ["y", "Y"]:
429+
current_color[0] -= color_step
430+
current_color[0] = torch.clamp(current_color[0], 0.0, 1.0)
431+
light.set_color(current_color)
432+
property_changed = True
433+
log_info(f"Red channel decreased to: {current_color[0]:.2f}")
434+
435+
# Color controls - Green channel
436+
elif key in ["g", "G"]:
437+
current_color[1] += color_step
438+
current_color[1] = torch.clamp(current_color[1], 0.0, 1.0)
439+
light.set_color(current_color)
440+
property_changed = True
441+
log_info(f"Green channel increased to: {current_color[1]:.2f}")
442+
elif key in ["h", "H"]:
443+
current_color[1] -= color_step
444+
current_color[1] = torch.clamp(current_color[1], 0.0, 1.0)
445+
light.set_color(current_color)
446+
property_changed = True
447+
log_info(f"Green channel decreased to: {current_color[1]:.2f}")
448+
449+
# Color controls - Blue channel
450+
elif key in ["b", "B"]:
451+
current_color[2] += color_step
452+
current_color[2] = torch.clamp(current_color[2], 0.0, 1.0)
453+
light.set_color(current_color)
454+
property_changed = True
455+
log_info(f"Blue channel increased to: {current_color[2]:.2f}")
456+
elif key in ["n", "N"]:
457+
current_color[2] -= color_step
458+
current_color[2] = torch.clamp(current_color[2], 0.0, 1.0)
459+
light.set_color(current_color)
460+
property_changed = True
461+
log_info(f"Blue channel decreased to: {current_color[2]:.2f}")
462+
463+
# Reset control
464+
elif key in ["r", "R"]:
465+
current_color = init_color.clone()
466+
current_intensity = init_intensity
467+
current_falloff = init_falloff
468+
light.set_local_pose(init_pose)
469+
light.set_color(current_color)
470+
light.set_intensity(torch.tensor(current_intensity))
471+
light.set_falloff(torch.tensor(current_falloff))
472+
property_changed = True
473+
log_info("Reset to initial light properties")
474+
475+
# Print current properties
476+
elif key in ["p", "P"]:
477+
translation = current_pose[:3]
478+
log_info("\n=== Current Light Properties ===")
479+
log_info(f"Position: {translation}")
480+
log_info(f"Color (RGB): {current_color.numpy()}")
481+
log_info(f"Intensity: {current_intensity:.2f}")
482+
log_info(f"Falloff: {current_falloff:.2f}")
483+
484+
# Update pose if translation changed
485+
if property_changed and not np.allclose(new_pose, current_pose):
486+
light_pose = torch.as_tensor(new_pose, dtype=torch.float32).unsqueeze_(
487+
0
488+
)
489+
light.set_local_pose(light_pose)
490+
491+
# Update simulation if any property changed
492+
if property_changed and vis_pose:
493+
sim.update(step=1)
494+
495+
except KeyboardInterrupt:
496+
if vis_pose:
497+
sim.remove_marker("light_marker")
498+
log_info("\nControl loop interrupted by user (Ctrl+C)")
499+
except Exception as e:
500+
log_error(f"Error in light control loop: {e}")
501+
finally:
502+
try:
503+
# Restore terminal settings
504+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
505+
except:
506+
pass
507+
log_info("Light control loop terminated")

0 commit comments

Comments
 (0)