Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
62 changes: 0 additions & 62 deletions .devcontainer/entrypoint.sh

This file was deleted.

3 changes: 0 additions & 3 deletions .devcontainer/nvidia/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
],
"service": "sim",
"runServices": ["sim"],
// Ports are published in docker/docker-compose.yml (sourced from docker/.env),
// so VS Code auto-forwards them without needing forwardPorts here.

"workspaceFolder": "/home/trickfire/simulations",
"remoteEnv": {
"HOST_WORKSPACE": "${localWorkspaceFolder}"
Expand Down
6 changes: 0 additions & 6 deletions .devcontainer/pixi.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
#!/bin/bash

# Gazebo plugin search path
export GZ_SIM_SYSTEM_PLUGIN_PATH="${CONDA_PREFIX}/lib${GZ_SIM_SYSTEM_PLUGIN_PATH:+:$GZ_SIM_SYSTEM_PLUGIN_PATH}"

# Switch to Cyclone DDS
# Fast-DDS calls pthread_setaffinity_np which macOS doesn't support
# flooding the log with 'Protocol family not supported' errors on
# every DDS thread startup
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
23 changes: 3 additions & 20 deletions .devcontainer/x_server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ parse_args() {
done
}

# Exits the script early (code 0) if a host Wayland compositor socket is
# available, since Vulkan/GL clients can talk to it directly without any
# in-container X server. FORCE_VNC always skips this and forces the VNC path.
try_wayland_passthrough() {
if [ -n "$FORCE_VNC" ]; then
DISPLAY="${FORCE_VNC_DISPLAY:-:77}"
Expand All @@ -75,16 +72,10 @@ try_wayland_passthrough() {
fi
}

# Sets BACKEND ("xorg"/"xvfb") and XORG_CONF based on available hardware.
detect_backend() {
if [ -n "$FORCE_VNC" ]; then
# FORCE_VNC always runs alongside a real host session, so real Xorg here
# (dummy, vkms-targeted, or real GPU driver - it doesn't matter which) has
# repeatedly ended up touching real display hardware and hijacking the
# host's screen. Xvfb is the only backend that's structurally incapable of
# touching /dev/dri at all, so FORCE_VNC always uses it, no exceptions.
BACKEND="xvfb"
XORG_CONF="/etc/X11/xorg.dummy.conf" # only used below to read screen resolution
XORG_CONF="/etc/X11/xorg.dummy.conf"
log "[X11] FORCE_VNC: using Xvfb unconditionally (no direct GPU access)"
elif [ -e /dev/nvmap ] && [ ! -d /dev/dri ]; then
BACKEND="xvfb"
Expand All @@ -99,8 +90,6 @@ detect_backend() {
fi
}

# No-GPU case: sets BACKEND and XORG_CONF, preferring a vkms virtual display
# over plain Xvfb when the kernel module is available.
detect_vkms_backend() {
BACKEND="xorg"
log "[X11] No GPU detected - trying vkms for DRI3-capable headless display"
Expand All @@ -120,7 +109,7 @@ detect_vkms_backend() {
if [ -z "$VKMS_CARD" ]; then
log "[X11] vkms unavailable, falling back to Xvfb (no real GPU access, no DRI3)"
BACKEND="xvfb"
XORG_CONF="/etc/X11/xorg.dummy.conf" # only used below to read screen resolution
XORG_CONF="/etc/X11/xorg.dummy.conf"
return
fi

Expand All @@ -129,7 +118,6 @@ detect_vkms_backend() {
sed "s|__VKMS_CARD__|$VKMS_CARD|" /etc/X11/xorg.vkms.conf >"$XORG_CONF"
}

# Reads SCREEN_WIDTH/SCREEN_HEIGHT/SCREEN_DEPTH out of $XORG_CONF.
parse_screen_resolution() {
SCREEN_MODE=$(grep -oP '(?<=Modes ")[^"]+' "$XORG_CONF" | head -1)
SCREEN_WIDTH=$(echo "$SCREEN_MODE" | cut -dx -f1)
Expand All @@ -144,7 +132,6 @@ parse_screen_resolution() {
start_services() {
trap cleanup SIGINT SIGTERM

# Start X server (Xorg or Xvfb determined above)
if [ "$BACKEND" = "xvfb" ]; then
log "[X11] Starting Xvfb on display ${DISPLAY} (${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH})"
start Xvfb "$DISPLAY" -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}"
Expand All @@ -153,22 +140,18 @@ start_services() {
start sudo Xorg "$DISPLAY" -noreset -config "$XORG_CONF"
fi
sleep 1
stty sane 2>/dev/null || true # Xorg alters terminal CR/LF settings
stty sane 2>/dev/null || true

# Start window manager
log "[WM] Starting Openbox window manager"
start openbox-session

# Start x11vnc server for VNC connection
log "[VNC] Starting x11vnc on port ${VNC_PORT}"
start x11vnc -display "$DISPLAY" -forever -shared -rfbport "$VNC_PORT" -nopw -xkb

# Start noVNC web client to serve the VNC desktop in a browser
log "[noVNC] Starting browser-based desktop on port ${NOVNC_PORT}"
start /usr/share/novnc/utils/novnc_proxy --vnc "localhost:${VNC_PORT}" --listen "${NOVNC_PORT}"
log "[noVNC] Desktop available at: http://localhost:${NOVNC_PORT}/vnc.html"

# Detach all background services so they survive this script exiting
disown "${PIDS[@]}" || true
log "[MAIN] All services started"
}
Expand Down
2 changes: 1 addition & 1 deletion cli/drpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


def _find_discord_socket_dir() -> str | None:
"""Return the directory containing a discord-ipc-N socket, or None if not found."""
"""Return the directory containing a discord-ipc-N socket, or None if not found"""
candidates = [
os.environ.get("XDG_RUNTIME_DIR"), # standard Linux user session
os.environ.get("TMPDIR"),
Expand Down
2 changes: 1 addition & 1 deletion cli/gazebo/create/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""sim create/update - robot package generation from OnShape."""
"""sim create/update - robot package generation from OnShape"""

from __future__ import annotations

Expand Down
2 changes: 1 addition & 1 deletion cli/gazebo/create/onshape.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def parse_onshape_url(url: str) -> tuple:


def _poll_job(dashboard_url: str, job_id: str) -> requests.Response:
"""Poll until the job is done; returns the streaming archive response."""
"""Poll until the job is done; returns the streaming archive response"""
deadline = time.monotonic() + _POLL_TIMEOUT
while time.monotonic() < deadline:
time.sleep(_POLL_INTERVAL)
Expand Down
2 changes: 1 addition & 1 deletion cli/gazebo/create/urdf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""URDF post-processing: xacro injection, mesh path rewriting, control generation."""
"""URDF post-processing: xacro injection, mesh path rewriting, control generation"""

import re
import xml.etree.ElementTree as ET
Expand Down
15 changes: 3 additions & 12 deletions cli/gazebo/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,7 @@ def _check_display() -> None:


def _configure_force_vnc_rendering(env: dict[str, str]) -> list[str]:
"""Force software GL rendering for Gazebo/OGRE2 when running under FORCE_VNC.

FORCE_VNC intentionally never touches real GPU/render-node devices - every
attempt to give it direct GPU access (Xorg with a real or virtual KMS
device, VirtualGL through the render node) has ended up interfering with
the host's real display. Mesa's software rasterizer is unaccelerated but
it's the only backend that's actually safe here.
"""
"""Force software GL rendering for Gazebo/OGRE2 when running under FORCE_VNC"""
if not os.environ.get("FORCE_VNC"):
return []

Expand All @@ -139,10 +132,10 @@ def _configure_force_vnc_rendering(env: dict[str, str]) -> list[str]:


def _setup_pixi_env() -> None:
"""Configure Gazebo plugin/resource paths and Qt platform for a pixi environment."""
"""Configure Gazebo plugin/resource paths and Qt platform for a pixi environment"""
pixi_dir = REPO_DIR / ".pixi"
if not pixi_dir.is_dir():
die(f"No pixi environment at {pixi_dir}\n Install dependencies first: pixi install")
die(f"No pixi environment at {pixi_dir}\nInstall dependencies first: pixi install")

conda_prefix = os.environ.get("CONDA_PREFIX", "")
if conda_prefix:
Expand All @@ -165,8 +158,6 @@ def _setup_pixi_env() -> None:
if ogre2_media not in paths:
os.environ["GZ_RENDERING_RESOURCE_PATH"] = ":".join([ogre2_media] + paths)

# The pixi Qt build ships only the xcb plugin; force it when the session
# sets QT_QPA_PLATFORM=wayland (common on Wayland desktops like Hyprland).
if os.environ.get("QT_QPA_PLATFORM") == "wayland":
os.environ["QT_QPA_PLATFORM"] = "xcb"

Expand Down
1 change: 0 additions & 1 deletion docker/.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ ROSBRIDGE_PORT=9090

# set to one if you want
# to force vnc startup
# (ignore wayland, existing xserver etc)
# FORCE_VNC=1
14 changes: 7 additions & 7 deletions gazebo/sim_common/sim_common/joint_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def wait_for_joints(self, timeout: float = DISCOVERY_TIMEOUT_S) -> bool:
return self._joints_ready.wait(timeout=timeout)

def discover_trajectory_topics(self) -> list[str]:
"""Return all currently advertised topics that carry JointTrajectory messages."""
"""Return all currently advertised topics that carry JointTrajectory messages"""
return [
topic
for topic, types in self.get_topic_names_and_types()
Expand Down Expand Up @@ -177,7 +177,7 @@ def _poll_discovery(self, attempt: int = 0) -> None:
self.root.after(self._POLL_MS, lambda: self._poll_discovery(attempt + 1))

def _refresh_topics(self) -> None:
"""Re-query available trajectory topics and update the combobox values."""
"""Re-query available trajectory topics and update the combobox values"""
topics = self.node.discover_trajectory_topics()
if self._topics_combo is not None:
self._topics_combo["values"] = topics
Expand Down Expand Up @@ -275,11 +275,11 @@ def _build_controls(self) -> None:
# ------------------------------------------------------------------

def _on_slider(self, joint: str, val: str) -> None:
"""Update the numeric label next to a slider whenever its value changes."""
"""Update the numeric label next to a slider whenever its value changes"""
self.value_labels[joint].config(text=f"{float(val):+.3f}")

def _send(self) -> None:
"""Read current slider positions and publish a trajectory command to the selected topic."""
"""Read current slider positions and publish a trajectory command to the selected topic"""
joints = self.node.joint_names
positions = [self.sliders[j].get() + self.node.possible_joints[j]["origin"] for j in joints]
topic = self._topic_var.get().strip()
Expand All @@ -290,7 +290,7 @@ def _send(self) -> None:
self.status_var.set(f"Sent to {topic}: {[f'{p:.2f}' for p in positions]}")

def _sync(self) -> None:
"""Sync sliders to the current joint positions reported by /joint_states."""
"""Sync sliders to the current joint positions reported by /joint_states"""
current = self.node.current_positions
if not current:
self.status_var.set("No joint state data available")
Expand All @@ -303,7 +303,7 @@ def _sync(self) -> None:
self.status_var.set("Synced sliders to current joint positions")

def _reset(self) -> None:
"""Zero all joint sliders and reset their display labels."""
"""Zero all joint sliders and reset their display labels"""
for joint in self.node.joint_names:
self.sliders[joint].set(0.0)
self.value_labels[joint].config(text="0.0")
Expand All @@ -312,7 +312,7 @@ def _reset(self) -> None:

def main() -> None:
"""Take in the filename,
Initialise ROS2, spin the node on a background thread, then run the Tkinter event loop."""
Initialise ROS2, spin the node on a background thread, then run the Tkinter event loop"""
if len(sys.argv) < 2:
print("Failed no path was provided")
sys.exit(1)
Expand Down
14 changes: 7 additions & 7 deletions gazebo/sim_common/sim_common/launch_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Shared launch utilities for simulation packages."""
"""Shared launch utilities for simulation packages"""

import os
import shutil
Expand All @@ -22,12 +22,12 @@


def log(msg):
"""Green info log for launch files."""
"""Green info log for launch files"""
print("\033[0;32m", "[INFO] [launch]: ", msg, "\x1b[0m", sep="")


def err(msg):
"""Red error log, exits with code 1."""
"""Red error log, exits with code 1"""
print("\033[0;31m", "[ERROR] [launch]: ", msg, "\x1b[0m", sep="")
sys.exit(1)

Expand All @@ -45,7 +45,7 @@ def get_asset(package, *parts):


def gz_supports_sim_command():
"""Return True when `gz` exists and exposes the `sim` subcommand."""
"""Return True when `gz` exists and exposes the `sim` subcommand"""
if shutil.which("gz") is None:
return False

Expand All @@ -64,7 +64,7 @@ def gz_supports_sim_command():


def process_robot_description(urdf_file, controller_config):
"""Run xacro over `urdf_file`, wiring in the controller config and platform plugin extension."""
"""Run xacro over `urdf_file`, wiring in the controller config and platform plugin extension"""
return xacro.process_file(
urdf_file,
mappings={
Expand Down Expand Up @@ -106,7 +106,7 @@ def gazebo_launch_actions(world_file, gz_gui_config, gui_launch_arg="gui"):


def spawn_robot_node(robot_name, robot_desc):
"""Node that spawns `robot_desc` into the running gz sim world as `robot_name`."""
"""Node that spawns `robot_desc` into the running gz sim world as `robot_name`"""
return Node(
package="ros_gz_sim",
executable="create",
Expand All @@ -126,7 +126,7 @@ def robot_state_publisher_node(robot_desc):


def clock_bridge_node():
"""ros_gz_bridge node that bridges the gz sim clock onto /clock."""
"""ros_gz_bridge node that bridges the gz sim clock onto /clock"""
return Node(
package="ros_gz_bridge",
executable="parameter_bridge",
Expand Down