Skip to content

Commit e35e766

Browse files
committed
WIP
1 parent 06fb337 commit e35e766

File tree

20 files changed

+692
-673
lines changed

20 files changed

+692
-673
lines changed

.github/workflows/generic.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
matrix:
1717
# See official Github documentation for details: https://shorturl.at/NJgsj
1818
OS: ["ubuntu-22.04", "ubuntu-24.04", "macos-15", "windows-cpu-4-core"]
19-
PYTHON_VERSION: ["3.10", "3.11", "3.12"]
19+
PYTHON_VERSION: ["3.10", "3.11", "3.12", "3.13"]
2020

2121
env:
2222
HF_HUB_DOWNLOAD_TIMEOUT: 60

.github/workflows/production.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
WANDB_API_KEY: ${{ secrets.WANDB_API_KEY }}
2323
HF_TOKEN: ${{ secrets.HF_TOKEN }}
2424
HF_HUB_DOWNLOAD_TIMEOUT: 60
25-
GENESIS_IMAGE_VER: "1_0"
25+
GENESIS_IMAGE_VER: "1_1"
2626
TIMEOUT_MINUTES: 180
2727

2828
steps:

examples/drone/interactive_drone.py

Lines changed: 38 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import argparse
2-
import numpy as np
3-
import genesis as gs
41
import time
52
import threading
3+
64
from pynput import keyboard
5+
import numpy as np
6+
7+
import genesis as gs
78

89

910
class DroneController:
1011
def __init__(self):
11-
self.thrust = 14468.429183500699 # Base hover RPM - constant hover
12-
self.rotation_delta = 200 # Differential RPM for rotation
13-
self.thrust_delta = 10 # Amount to change thrust by when accelerating/decelerating
12+
self.thrust = 14475.8 # Base hover RPM - constant hover
13+
self.rotation_delta = 200.0 # Differential RPM for rotation
14+
self.thrust_delta = 10.0 # Amount to change thrust by when accelerating/decelerating
1415
self.running = True
1516
self.rpms = [self.thrust] * 4
1617
self.pressed_keys = set()
@@ -93,46 +94,37 @@ def update_thrust(self):
9394

9495
def run_sim(scene, drone, controller):
9596
while controller.running:
96-
try:
97-
# Update drone with current RPMs
98-
rpms = controller.update_thrust()
99-
drone.set_propellels_rpm(rpms)
97+
# Update drone with current RPMs
98+
rpms = controller.update_thrust()
99+
drone.set_propellels_rpm(rpms)
100100

101-
# Update physics
102-
scene.step()
101+
# Update physics
102+
scene.step(refresh_visualizer=False)
103103

104-
time.sleep(1 / 60) # Limit simulation rate
105-
except Exception as e:
106-
print(f"Error in simulation loop: {e}")
107-
108-
if scene.viewer:
109-
scene.viewer.stop()
104+
# Limit simulation rate
105+
time.sleep(1.0 / scene.viewer.max_FPS)
110106

111107

112108
def main():
113-
parser = argparse.ArgumentParser()
114-
parser.add_argument("-v", "--vis", action="store_true", default=True, help="Enable visualization (default: True)")
115-
parser.add_argument("-m", "--mac", action="store_true", default=False, help="Running on MacOS (default: False)")
116-
args = parser.parse_args()
117-
118109
# Initialize Genesis
119110
gs.init(backend=gs.cpu)
120111

121-
# Create scene with initial camera view
122-
viewer_options = gs.options.ViewerOptions(
123-
camera_pos=(0.0, -4.0, 2.0), # Now behind the drone (negative Y)
124-
camera_lookat=(0.0, 0.0, 0.5),
125-
camera_fov=45,
126-
max_FPS=60,
127-
)
128-
112+
# Create scene
129113
scene = gs.Scene(
130114
sim_options=gs.options.SimOptions(
131115
dt=0.01,
132116
gravity=(0, 0, -9.81),
133117
),
134-
viewer_options=viewer_options,
135-
show_viewer=args.vis,
118+
viewer_options=gs.options.ViewerOptions(
119+
camera_pos=(0.0, -2.0, 1.0),
120+
camera_lookat=(0.0, 0.0, 0.3),
121+
camera_fov=45,
122+
max_FPS=60,
123+
),
124+
vis_options=gs.options.VisOptions(
125+
show_world_frame=False,
126+
),
127+
show_viewer=True,
136128
)
137129

138130
# Add entities
@@ -146,38 +138,32 @@ def main():
146138

147139
scene.viewer.follow_entity(drone)
148140

149-
# Build scene
150-
scene.build()
151-
152141
# Initialize controller
153142
controller = DroneController()
154143

144+
# Start keyboard listener.
145+
# Note that instantiating the listener after building the scene causes segfault on MacOS.
146+
listener = keyboard.Listener(on_press=controller.on_press, on_release=controller.on_release)
147+
listener.start()
148+
149+
# Build scene
150+
scene.build()
151+
155152
# Print control instructions
156153
print("\nDrone Controls:")
157154
print("↑ - Move Forward (North)")
158155
print("↓ - Move Backward (South)")
159156
print("← - Move Left (West)")
160157
print("→ - Move Right (East)")
158+
print("space - Increase RPM")
159+
print("shift - Decrease RPM")
161160
print("ESC - Quit\n")
162161
print("Initial hover RPM:", controller.thrust)
163162

164-
# Start keyboard listener
165-
listener = keyboard.Listener(on_press=controller.on_press, on_release=controller.on_release)
166-
listener.start()
167-
168-
if args.mac:
169-
# Run simulation in another thread
170-
sim_thread = threading.Thread(target=run_sim, args=(scene, drone, controller))
171-
sim_thread.start()
172-
173-
if args.vis:
174-
scene.viewer.start()
163+
# Run simulation in another thread
164+
threading.Thread(target=run_sim, args=(scene, drone, controller)).start()
165+
scene.viewer.run()
175166

176-
# Wait for threads to finish
177-
sim_thread.join()
178-
else:
179-
# Run simulation in main thread
180-
run_sim(scene, drone, controller)
181167
listener.stop()
182168

183169

examples/rigid/single_franka_batch_render.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ def main():
4242
pos=(1.5, 0.5, 1.5),
4343
lookat=(0.0, 0.0, 0.5),
4444
fov=45,
45-
GUI=True,
45+
GUI=args.vis,
4646
)
4747
cam_0.attach(franka.links[6], trans_to_T(np.array([0.0, 0.5, 0.0])))
4848
cam_1 = scene.add_camera(
4949
res=(512, 512),
5050
pos=(1.5, -0.5, 1.5),
5151
lookat=(0.0, 0.0, 0.5),
5252
fov=45,
53-
GUI=True,
53+
GUI=args.vis,
5454
)
5555
scene.add_light(
5656
pos=[0.0, 0.0, 1.5],
@@ -81,8 +81,8 @@ def main():
8181
rgba, depth, _, _ = scene.render_all_cameras(rgb=True, depth=True)
8282
exporter.export_frame_all_cameras(i, rgb=rgba, depth=depth)
8383
else:
84-
rgba, depth, _, _ = cam_0.render(rgb=True, depth=True)
85-
exporter.export_frame_single_camera(i, cam_0.idx, rgb=rgba, depth=depth)
84+
rgba, depth, _, _ = cam_1.render(rgb=True, depth=True)
85+
exporter.export_frame_single_camera(i, cam_1.idx, rgb=rgba, depth=depth)
8686

8787

8888
if __name__ == "__main__":

genesis/engine/entities/rigid_entity/rigid_link.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from typing import TYPE_CHECKING
22

33
import numpy as np
4-
from numpy.typing import ArrayLike
54
import taichi as ti
65
import torch
6+
from numpy.typing import ArrayLike
77

88
import genesis as gs
99
import trimesh

genesis/engine/scene.py

Lines changed: 59 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import os
2+
import pickle
3+
import time
24

35
import numpy as np
46
import torch
5-
import pickle
6-
import time
77
import taichi as ti
8+
from numpy.typing import ArrayLike
89

910
import genesis as gs
1011
import genesis.utils.geom as gu
@@ -434,95 +435,84 @@ def link_entities(
434435

435436
if child_link._parent_idx != -1:
436437
gs.logger.warning(
437-
"Child entity already has a parent link. This may cause the entity to break into parts. Make sure this operation is intended."
438+
"Child entity already has a parent link. This may cause the entity to break into parts. Make sure "
439+
"this operation is intended."
438440
)
439441
child_link._parent_idx = parent_link.idx
440442
parent_link._child_idxs.append(child_link.idx)
441443

442444
@gs.assert_unbuilt
443445
def add_light(
444446
self,
445-
morph: Morph,
446-
color=(1.0, 1.0, 1.0, 1.0),
447-
intensity=20.0,
448-
revert_dir=False,
449-
double_sided=False,
450-
beam_angle=180.0,
447+
*,
448+
morph: Morph | None = None,
449+
color: ArrayLike | None = (1.0, 1.0, 1.0, 1.0),
450+
intensity: float = 20.0,
451+
revert_dir: bool | None = False,
452+
double_sided: bool | None = False,
453+
beam_angle: float | None = 180.0,
454+
pos: ArrayLike | None = None,
455+
dir: ArrayLike | None = None,
456+
directional: bool | None = None,
457+
castshadow: bool | None = None,
458+
cutoff: float | None = None,
451459
):
452460
"""
453-
Add a light to the scene. Note that lights added this way can be instantiated from morphs
454-
(supporting `gs.morphs.Primitive` or `gs.morphs.Mesh`), and will only be used by the RayTracer renderer.
461+
Add a light to the scene.
462+
463+
Warning
464+
-------
465+
The signature of this method is different depending on the renderer being used, i.e.:
466+
- RayTracer: 'add_light(self, morph, color, intensity, revert_dir, double_sided, beam_angle)'
467+
- BatchRender: 'add_ligth(self, pos, dir, intensity, directional, castshadow, cutoff)'
468+
- Rasterizer: **Unsupported**
455469
456470
Parameters
457471
----------
458472
morph : gs.morphs.Morph
459-
The morph of the light. Must be an instance of `gs.morphs.Primitive` or `gs.morphs.Mesh`.
473+
The morph of the light. Must be an instance of `gs.morphs.Primitive` or `gs.morphs.Mesh`. Only supported by
474+
RayTracer.
460475
color : tuple of float, shape (3,)
461-
The color of the light, specified as (r, g, b).
476+
The color of the light, specified as (r, g, b). Only supported by RayTracer.
462477
intensity : float
463478
The intensity of the light.
464479
revert_dir : bool
465480
Whether to revert the direction of the light. If True, the light will be emitted towards the mesh's inside.
481+
Only supported by RayTracer.
466482
double_sided : bool
467-
Whether to emit light from both sides of surface.
483+
Whether to emit light from both sides of surface. Only supported by RayTracer.
468484
beam_angle : float
469-
The beam angle of the light.
470-
"""
471-
if isinstance(self.renderer_options, gs.renderers.BatchRenderer):
472-
gs.logger.warning(
473-
"This add_light() function is only supported when NOT using BatchRenderer."
474-
"Please use add_light(self, pos, dir, intensity, directional, castshadow, cutoff) instead."
475-
)
476-
return
477-
478-
if self.visualizer.raytracer is None:
479-
gs.logger.warning("Light is only supported by RayTracer renderer.")
480-
return
481-
482-
if not isinstance(morph, (gs.morphs.Primitive, gs.morphs.Mesh)):
483-
gs.raise_exception("Light morph only supports `gs.morphs.Primitive` or `gs.morphs.Mesh`.")
484-
485-
mesh = gs.Mesh.from_morph_surface(morph, gs.surfaces.Plastic(smooth=False))
486-
self.visualizer.raytracer.add_mesh_light(
487-
mesh, color, intensity, morph.pos, morph.quat, revert_dir, double_sided, beam_angle
488-
)
489-
490-
@gs.assert_unbuilt
491-
def add_light(
492-
self,
493-
pos,
494-
dir,
495-
intensity,
496-
directional,
497-
castshadow,
498-
cutoff,
499-
):
500-
"""
501-
Add a light to the scene for batch renderer.
502-
503-
Parameters
504-
----------
485+
The beam angle of the light. Only supported by RayTracer.
505486
pos : tuple of float, shape (3,)
506-
The position of the light, specified as (x, y, z).
487+
The position of the light, specified as (x, y, z). Only supported by BatchRenderer.
507488
dir : tuple of float, shape (3,)
508-
The direction of the light, specified as (x, y, z).
489+
The direction of the light, specified as (x, y, z). Only supported by BatchRenderer.
509490
intensity : float
510-
The intensity of the light.
491+
The intensity of the light. Only supported by BatchRenderer.
511492
directional : bool
512-
Whether the light is directional.
493+
Whether the light is directional. Only supported by BatchRenderer.
513494
castshadow : bool
514-
Whether the light casts shadows.
495+
Whether the light casts shadows. Only supported by BatchRenderer.
515496
cutoff : float
516-
The cutoff angle of the light in degrees.
497+
The cutoff angle of the light in degrees. Only supported by BatchRenderer.
517498
"""
518-
if not isinstance(self.renderer_options, gs.renderers.BatchRenderer):
519-
gs.logger.warning(
520-
"This add_light() function is only supported when using BatchRenderer."
521-
"Please use add_light(self, morph, color, intensity, revert_dir, double_sided, beam_angle) instead."
499+
if self._visualizer.batch_renderer is not None:
500+
if any(map(lambda e: e is None, (pos, dir, intensity, directional, castshadow, cutoff))):
501+
gs.raise_exception("Input arguments do not complain with expected signature when using 'BatchRenderer'")
502+
503+
self.visualizer.add_light(pos, dir, intensity, directional, castshadow, cutoff)
504+
elif self.visualizer.raytracer is not None:
505+
if any(map(lambda e: e is None, (morph, color, intensity, revert_dir, double_sided, beam_angle))):
506+
gs.raise_exception("Input arguments do not complain with expected signature when using 'RayTracer'")
507+
if not isinstance(morph, (gs.morphs.Primitive, gs.morphs.Mesh)):
508+
gs.raise_exception("Light morph only supports `gs.morphs.Primitive` or `gs.morphs.Mesh`.")
509+
510+
mesh = gs.Mesh.from_morph_surface(morph, gs.surfaces.Plastic(smooth=False))
511+
self.visualizer.raytracer.add_mesh_light(
512+
mesh, color, intensity, morph.pos, morph.quat, revert_dir, double_sided, beam_angle
522513
)
523-
return
524-
525-
self.visualizer.add_light(pos, dir, intensity, directional, castshadow, cutoff)
514+
else:
515+
gs.raise_exception("Adding lights is only supported by 'RayTracer' and 'BatchRenderer'.")
526516

527517
@gs.assert_unbuilt
528518
def add_camera(
@@ -780,7 +770,8 @@ def _reset(self, state: SimState | None = None, *, envs_idx=None):
780770
self._forward_ready = True
781771
self._reset_grad()
782772

783-
# TODO: sets _t = -1; not sure this is env isolation safe
773+
# Clear the entire cache of the visualizer.
774+
# TODO: Could be optimized to only clear cache associated the the environments being reset.
784775
self._visualizer.reset()
785776

786777
# TODO: sets _next_particle = 0; not sure this is env isolation safe
@@ -1110,8 +1101,11 @@ def render_all_cameras(self, rgb=True, depth=False, normal=False, segmentation=F
11101101
Returns:
11111102
A tuple of tensors of shape (n_envs, H, W, 3) if rgb is not None,
11121103
otherwise a list of tensors of shape (n_envs, H, W, 1) if depth is not None.
1113-
If n_envs ==0, the first dimension of the tensor is squeezed.
1104+
If n_envs == 0, the first dimension of the tensor is squeezed.
11141105
"""
1106+
if self._visualizer.batch_renderer is None:
1107+
gs.raise_exception("Method only supported by 'BatchRenderer'")
1108+
11151109
return self._visualizer.batch_renderer.render(rgb, depth, normal, segmentation, force_render)
11161110

11171111
@gs.assert_built

genesis/engine/solvers/avatar_solver.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ def _kernel_step(self):
5757

5858
@ti.kernel
5959
def _kernel_forward_kinematics_links_geoms(self, envs_idx: ti.types.ndarray()):
60-
for i_b in envs_idx:
60+
for i_b_ in range(envs_idx.shape[0]):
61+
i_b = envs_idx[i_b_]
62+
6163
self._func_forward_kinematics(
6264
i_b,
6365
self.links_state,

0 commit comments

Comments
 (0)