بنقرة واحدة
vsg
Set up and run ChVisualSystemVSG for 3D rendering — window, camera, grid, sky, lights, and render loop.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Set up and run ChVisualSystemVSG for 3D rendering — window, camera, grid, sky, lights, and render loop.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Entry point for FSI-coupled hybrid plans (plan_type=fsi_in_scene) — any scene combining SPH fluid (water tanks, dam-break, wave channels) with multi-body dynamics, optionally with a wheeled vehicle. Pick this over mbs_in_scene whenever the plan involves a fluid domain (scene_objects with domain_type starting "sph_") or an FSI body registration (scene_objects with fsi_registration set). Routes to fsi/sph (always), veh/wheeled_vehicle (when vehicle present), and enforces FSI-specific invariants distinct from generic mbs_in_scene rigid scenes.
Entry point for rigid-body hybrid plans (plan_type=mbs_in_scene) combining a robot or vehicle with scene assets — NO fluid coupling. Routes to the correct domain skills and defines only high-level invariants. Use core/fsi_in_scene instead when the plan involves SPH fluid or FSI body registration.
Entry point for pure multi-body simulation plans (plan_type=mbs). Routes the agent to the correct mechanics, system, and camera skills and defines only high-level invariants.
Entry point for static scene plans (plan_type=scene). Routes the agent to the correct scene, system, and camera skills and defines only high-level invariants.
Set up SPH-based Fluid-Structure Interaction (FSI): create ChFsiFluidSystemSPH and ChFsiSystemSPH, configure fluid and SPH parameters, seed fluid particles with hydrostatic initialization, add container BCE boundary markers, register floating rigid bodies, optionally couple with a wheeled vehicle, and advance with sysFSI.DoStepDynamics(dT).
Translate a `geometry_relations` entry from the plan into correct PyChrono coordinate code. Read this whenever the plan declares a relation_name you have not previously encoded, and especially BEFORE writing SetPos / camera placement for any body that participates in a multi-body geometric constraint. Each subsection below is one canonical pattern named exactly as it appears in `plan.geometry_relations[i].relation_name`.
| name | vsg |
| description | Set up and run ChVisualSystemVSG for 3D rendering — window, camera, grid, sky, lights, and render loop. |
| compatibility | pychrono >= 8.0 |
| metadata | {"domain":"vis"} |
allowed_classes:
allowed_methods:
canonical_examples:
Create and configure a ChVisualSystemVSG window for real-time 3D rendering of PyChrono simulations. Covers window setup, camera placement, sky/grid/lighting, and the render loop.
Whenever the simulation needs a 3D visualization window (non-headless). This skill covers the generic VSG system. For wheeled vehicle scenes, prefer veh.ChWheeledVehicleVisualSystemVSG (see veh/wheeled_vehicle skill).
import pychrono.core as chrono
import pychrono.vsg3d as chronovsg
ChVisualSystemVSG is order-sensitive. The following sequence MUST be respected:
1. Create vis = chronovsg.ChVisualSystemVSG()
2. Attach vis.AttachSystem(sys)
3. Configure SetWindowSize, SetWindowTitle, SetCameraAngleDeg, ...
4. AddCamera vis.AddCamera(pos, target) <-- BEFORE Initialize
5. AddGrid vis.AddGrid(...) <-- BEFORE Initialize
6. Initialize vis.Initialize()
7. Render loop while vis.Run(): ...
Hard rules:
AddCamera() MUST be called BEFORE Initialize().
Calling it after Initialize() triggers:
Function ChVisualSystemVSG::AddCamera can only be called before initialization!
Do NOT call vis.BindAll() after vis.Initialize(). This has been
observed to SIGSEGV at vsg::BindGraphicsPipeline::record on the
first render when combined with vis.EnableShadows(True) and any body
carrying a mesh visual (ChBodyEasyMesh, ChVisualShapeTriangleMesh,
ChVisualShapeModelFile). Either feature alone is fine; the
EnableShadows + BindAll combination is not. The project's own
reference demo demo/scene/demo_SEN_HMMWV_offroad_vsg.py calls
vis.Initialize() and nothing else — match that pattern. BindAll()
is intended for the Chrono collision system (chrono.ChSystem::Initialize
already triggers it) and for the ChCollisionSystem.BindAll() shown in
the demo. It is NOT needed on ChVisualSystemVSG.
All Set* configuration calls go between AttachSystem and Initialize. Setting properties after Initialize may silently have no effect.
vis = chronovsg.ChVisualSystemVSG()
vis.AttachSystem(sys)
vis.SetWindowSize(1280, 720) # width, height in pixels
vis.SetWindowTitle("My Simulation")
vis.SetCameraAngleDeg(45) # field-of-view angle in degrees
vis.SetCameraVertical(chrono.CameraVerticalDir_Z) # Z-up (default for PyChrono)
| Wrong | Correct | Why |
|---|---|---|
vis.SetCameraAngle(45) | vis.SetCameraAngleDeg(45) | SetCameraAngle does not exist |
vis.UseSkydome(True) | vis.EnableSkyTexture() | UseSkydome does not exist |
vis.Update() | vis.Run() + render loop | Update does not exist on VSG |
vis.AddCOI(...) | (remove the call) | AddCOI does not exist |
# Add camera BEFORE Initialize — returns a camera id (int)
cam_id = vis.AddCamera(
chrono.ChVector3d(8, -8, 4), # eye position
chrono.ChVector3d(0, 0, 0), # look-at target (default: origin)
)
Move the camera at runtime (after Initialize):
vis.SetCameraPosition(cam_id, chrono.ChVector3d(new_x, new_y, new_z))
vis.SetCameraTarget(cam_id, chrono.ChVector3d(tx, ty, tz))
# or update both at once:
vis.UpdateCamera(cam_id, new_pos, new_target)
# Option 1: procedural sky dome (default mode)
vis.EnableSkyTexture() # SkyMode::DOME
# Option 2: custom sky dome texture
vis.SetSkyDomeTexture("sky_texture.hdr", 0.0) # filename, sun azimuth (rad)
# Option 3: sky box
vis.SetSkyBoxTexture("skybox.hdr", 0.0)
# Option 4: solid background color (no sky)
vis.SetBackgroundColor(chrono.ChColor(0.2, 0.3, 0.4))
UseSkydome(True) does NOT exist. Use EnableSkyTexture() instead.
vis.SetLightIntensity(1.0) # global intensity [0..∞]
vis.SetLightDirection(1.5, 0.8) # azimuth (rad), elevation (rad)
vis.EnableShadows(True) # optional shadow mapping
vis.AddGrid(
2.0, 2.0, # x_step, y_step (meters between lines)
20, 20, # nx, ny (number of cells in each direction)
chrono.ChCoordsysd(), # position/orientation — ChCoordsysd, NOT ChVector3d
chrono.ChColor(0.4, 0.4, 0.4), # grid line color
)
Correct signature: AddGrid(x_step, y_step, nx, ny, ChCoordsysd, ChColor)
| Wrong | Correct | Why |
|---|---|---|
AddGrid(10.0, 100, ChColor(...), True, True, True) | AddGrid(10.0, 10.0, 20, 20, ChCoordsysd(), ChColor(...)) | Wrong param count and types |
5th arg: ChVector3d(0,0,0) | 5th arg: chrono.ChCoordsysd() | Must be ChCoordsysd (pos+rot), not ChVector3d |
To place the grid at a custom position/orientation:
grid_frame = chrono.ChCoordsysd(
chrono.ChVector3d(0, 0, 0), # position
chrono.QUNIT, # rotation (identity)
)
vis.AddGrid(2.0, 2.0, 20, 20, grid_frame, chrono.ChColor(0.4, 0.4, 0.4))
run_recording_loop, do NOT write while vis.Run(): by handThe low-level VSG render primitives are vis.Run() + vis.BeginScene()
vis.Render() + vis.EndScene(). Do not compose your own main loop
out of them. The project provides a single helper that owns the loop,
throttles rendering, pumps the sensor manager, and runs mp4 recorder
cleanup in a finally block:from chrono_code.utils import run_recording_loop
run_recording_loop(
sys,
duration=10.0,
time_step=0.001,
vis=vis, # any ChVisualSystemVSG subclass (generic, vehicle, etc.)
manager=manager, # optional: ChSensorManager
recorders=[rec_a, rec_b],# optional: list of CameraRecorder from setup_preview_camera
render_fps=50.0, # 50 Hz VSG rendering regardless of physics dt
)
What run_recording_loop does for you:
while sys.GetChTime() < duration AND (vis is None OR vis.Run()):
exit on either condition.vis.BeginScene()/Render()/EndScene() every N = round(1/(dt*render_fps))
physics steps — not every step.sys.DoStepDynamics(time_step) every iteration only when
step_fn= is not passed. If you pass step_fn=, the default
DoStepDynamics call is REPLACED by your callback — the callback now
owns the physics advance. See the "step_fn owns the advance" rule
below.manager.Update() + recorder.tick() for each recorder AFTER
the physics step, so sensors see post-step body poses.recorder.close() runs in a finally — essential for
mp4 moov atom finalization under timeout.Why this matters: the generated simulation.py runs under a 300 s
ExecutionAgent wall-clock timeout. On timeout the subprocess gets
SIGKILL, which bypasses finally blocks. A naive inline loop that
renders every physics step takes > 300 s wall-clock for a 30 s sim
at 0.001 dt (VSG pegged to display refresh), triggers the timeout, loses
any in-flight mp4 frames, and the VLM review step then rejects the
48-byte stub with HTTP 400. run_recording_loop(render_fps=50) renders
1 frame per 20 physics steps, cutting the cost 20× so the sim finishes
in ~30 s wall-clock and finally has a chance to write moov.
step_fn owns the physics advance — READ BEFORE PASSING step_fn=When you pass step_fn=..., run_recording_loop no longer calls
sys.DoStepDynamics(time_step) for you — the default step_fn (which
does exactly that one call) is replaced wholesale by your callback.
The symptom of a step_fn that forgets to advance physics is that
sys.GetChTime() stays at 0.0 forever, the loop never hits the
sim_time >= duration exit, and vis renders a frozen scene. The
robot / motors look wired but nothing moves.
Two valid shapes, and they are not interchangeable:
Shape A — URDF robot / raw ChBody scenes (no vehicle wrapper). Your
step_fn must end with an explicit sys.DoStepDynamics(time_step):
from chrono_code.utils import run_recording_loop
def robot_step(step_index, sim_time):
go2.actuate(stand_action) # motor command / policy output
sys.DoStepDynamics(time_step) # REQUIRED — no one else advances physics
run_recording_loop(
sys, duration=10.0, time_step=1e-3,
vis=vis, manager=manager, step_fn=robot_step, render_fps=50.0,
)
Shape B — vehicle / terrain scenes with Synchronize/Advance. The
veh.* wrapper's Advance() internally calls DoStepDynamics on the
wrapper-owned ChSystem. Do NOT also call sys.DoStepDynamics() here —
that double-steps the physics with stale driver inputs.
from chrono_code.utils import run_recording_loop
def vehicle_step(step_index, sim_time):
driver_inputs = driver.GetInputs()
driver.Synchronize(sim_time)
terrain.Synchronize(sim_time)
hmmwv.Synchronize(sim_time, driver_inputs, terrain)
vis.Synchronize(sim_time, driver_inputs)
driver.Advance(time_step)
terrain.Advance(time_step)
hmmwv.Advance(time_step) # internally calls DoStepDynamics
vis.Advance(time_step)
run_recording_loop(
hmmwv.GetSystem(),
duration=30.0,
time_step=1e-3,
vis=vis,
manager=manager,
recorders=recorders,
step_fn=vehicle_step,
render_fps=50.0,
)
Rule of thumb: if your scene has no veh.* wrapper whose Advance()
internally ticks the system, your step_fn must call
sys.DoStepDynamics(time_step) itself. If unsure, you almost certainly
want Shape A.
If the simulation needs per-frame PNG output (not mp4), pass an
on_step callback that calls vis.WriteImageToFile():
vis.SetImageOutput(True)
vis.SetImageOutputDirectory("output/frames")
run_recording_loop(
sys, duration=5.0, time_step=0.001,
vis=vis,
on_step=lambda i, t: vis.WriteImageToFile(),
)
For completeness, the primitives run_recording_loop composes under
the hood are vis.Run(), vis.BeginScene(), vis.Render(),
vis.EndScene(). vis.Update() does NOT exist on VSG. Unless you have
a documented reason to inline the loop, use run_recording_loop.
vis.HideLogo() # remove Chrono logo watermark
vis.EnableShadows(True) # shadow mapping
vis.SetTargetRenderFPS(60.0) # hint for render pacing
vis.EnableFullscreen(True) # fullscreen mode
vis.SetGuiFontSize(14) # GUI text size
vis.SetModelScale(1.0) # global model scale
import pychrono.core as chrono
import pychrono.vsg3d as chronovsg
from chrono_code.utils import run_recording_loop
sys = chrono.ChSystemNSC()
sys.SetGravitationalAcceleration(chrono.ChVector3d(0, 0, -9.81))
# ... add bodies, joints, etc. ...
vis = chronovsg.ChVisualSystemVSG()
vis.AttachSystem(sys)
vis.SetWindowSize(1280, 720)
vis.SetWindowTitle("Simulation")
vis.SetCameraAngleDeg(45)
vis.SetCameraVertical(chrono.CameraVerticalDir_Z)
vis.SetLightIntensity(1.0)
vis.EnableSkyTexture()
vis.AddGrid(2.0, 2.0, 20, 20, chrono.ChCoordsysd(), chrono.ChColor(0.4, 0.4, 0.4))
vis.AddCamera(chrono.ChVector3d(8, -8, 4), chrono.ChVector3d(0, 0, 0))
vis.Initialize()
run_recording_loop(sys, duration=10.0, time_step=0.001, vis=vis, render_fps=50.0)
mbs/system_create — ChSystem creation (must exist before AttachSystem)mbs/simulation_loop — data logging and post-processing patternssens/sensor_manager — if combining VSG window with sensor cameras (separate concerns)sens/camera — sensor camera recording (VSG window is NOT a sensor camera)