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
6 changes: 3 additions & 3 deletions .github/workflows/build-viewer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defaults:

env:
PYTHON_VERSION: "3.10"
PACKAGE_DIR: examples/rust/custom_callback
PACKAGE_DIR: dimos

# ---------------------------------------------------------------------------
jobs:
Expand Down Expand Up @@ -74,8 +74,8 @@ jobs:
target/
key: ${{ runner.os }}-cargo-check-${{ hashFiles('**/Cargo.lock') }}

- run: cargo check -p custom_callback
- run: cargo test -p custom_callback
- run: cargo check -p dimos-viewer
- run: cargo test -p dimos-viewer

# -------------------------------------------------------------------
# 2. Build wheels per platform
Expand Down
14 changes: 13 additions & 1 deletion Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2227,7 +2227,6 @@ name = "custom_callback"
version = "0.30.0-alpha.1+dev"
dependencies = [
"bincode",
"clap",
"mimalloc",
"parking_lot",
"rerun",
Expand Down Expand Up @@ -3109,6 +3108,19 @@ dependencies = [
"subtle",
]

[[package]]
name = "dimos-viewer"
version = "0.30.0-alpha.1+dev"
dependencies = [
"bincode",
"clap",
"mimalloc",
"parking_lot",
"rerun",
"serde",
"tokio",
]

[[package]]
name = "directories"
version = "6.0.0"
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/utils/*",
"crates/viewer/*",
"docs/snippets",
"dimos",
"examples/rust/*",
"rerun_py",
"run_wasm",
Expand Down
42 changes: 42 additions & 0 deletions dimos/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = "dimos-viewer"
version = "0.30.0-alpha.1+dev"
edition = "2024"
rust-version = "1.92"
license = "MIT OR Apache-2.0"
publish = false
description = "DimOS Interactive Viewer — custom Rerun viewer with LCM click-to-navigate"

[[bin]]
name = "dimos-viewer"
path = "src/viewer.rs"

[lib]
name = "dimos_viewer"
path = "src/lib.rs"

[features]
default = []
analytics = ["rerun/analytics"]

[dependencies]
rerun = { path = "../crates/top/rerun", default-features = false, features = [
"native_viewer",
"run",
"server",
] }

clap = { workspace = true, features = ["derive"] }
bincode.workspace = true
mimalloc.workspace = true
parking_lot.workspace = true
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = [
"io-util",
"macros",
"net",
"rt-multi-thread",
"signal",
"sync",
"time",
] }
File renamed without changes.
1 change: 1 addition & 0 deletions dimos/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod interaction;
196 changes: 196 additions & 0 deletions dimos/src/viewer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

use clap::Parser;
use dimos_viewer::interaction::{LcmPublisher, click_event_from_ms};
use rerun::external::{eframe, re_crash_handler, re_grpc_server, re_log, re_memory, re_viewer};

#[global_allocator]
static GLOBAL: re_memory::AccountingAllocator<mimalloc::MiMalloc> =
re_memory::AccountingAllocator::new(mimalloc::MiMalloc);

/// LCM channel for click events (follows RViz convention)
const LCM_CHANNEL: &str = "/clicked_point#geometry_msgs.PointStamped";
/// Minimum time between click events (debouncing)
const CLICK_DEBOUNCE_MS: u64 = 100;
/// Maximum rapid clicks to log as warning
const RAPID_CLICK_THRESHOLD: usize = 5;
/// Default gRPC listen port (9877 to avoid conflict with stock Rerun on 9876)
const DEFAULT_PORT: u16 = 9877;

/// DimOS Interactive Viewer — a custom Rerun viewer with LCM click-to-navigate.
///
/// Accepts the same CLI flags as the stock `rerun` binary so it can be spawned
/// seamlessly via `rerun_bindings.spawn(executable_name="dimos-viewer")`.
#[derive(Parser, Debug)]
#[command(name = "dimos-viewer", version, about)]
struct Args {
/// The gRPC port to listen on for incoming SDK connections.
#[arg(long, default_value_t = DEFAULT_PORT)]
port: u16,

/// An upper limit on how much memory the viewer should use.
/// When this limit is reached, the oldest data will be dropped.
/// Examples: "75%", "16GB".
#[arg(long, default_value = "75%")]
memory_limit: String,

/// An upper limit on how much memory the gRPC server should use.
/// Examples: "1GiB", "50%".
#[arg(long, default_value = "1GiB")]
server_memory_limit: String,

/// Hide the Rerun welcome screen.
#[arg(long)]
hide_welcome_screen: bool,

/// Hint that data will arrive shortly (suppresses "waiting for data" message).
#[arg(long)]
expect_data_soon: bool,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();

let main_thread_token = re_viewer::MainThreadToken::i_promise_i_am_on_the_main_thread();
re_log::setup_logging();
re_crash_handler::install_crash_handlers(re_viewer::build_info());

// Listen for gRPC connections from Rerun's logging SDKs.
let listen_addr = format!("0.0.0.0:{}", args.port);
re_log::info!("Listening for SDK connections on {listen_addr}");
let rx_log = re_grpc_server::spawn_with_recv(
listen_addr.parse()?,
Default::default(),
re_grpc_server::shutdown::never(),
);

// Create LCM publisher for click events
let lcm_publisher = LcmPublisher::new(LCM_CHANNEL.to_string())
.expect("Failed to create LCM publisher");
re_log::info!("LCM publisher created for channel: {LCM_CHANNEL}");

// State for debouncing and rapid click detection
let last_click_time = Rc::new(RefCell::new(Instant::now()));
let rapid_click_count = Rc::new(RefCell::new(0usize));

let mut native_options = re_viewer::native::eframe_options(None);
native_options.viewport = native_options
.viewport
.with_app_id("rerun_example_custom_callback");

let app_env = re_viewer::AppEnvironment::Custom("DimOS Interactive Viewer".to_owned());

let startup_options = re_viewer::StartupOptions {
on_event: Some(Rc::new({
let last_click_time = last_click_time.clone();
let rapid_click_count = rapid_click_count.clone();

move |event: re_viewer::ViewerEvent| {
if let re_viewer::ViewerEventKind::SelectionChange { items } = event.kind {
let mut has_position = false;
let mut no_position_count = 0;

for item in items {
match item {
re_viewer::SelectionChangeItem::Entity {
entity_path,
view_name: _,
position: Some(pos),
..
} => {
has_position = true;

// Debouncing
let now = Instant::now();
let elapsed = now.duration_since(*last_click_time.borrow());

if elapsed < Duration::from_millis(CLICK_DEBOUNCE_MS) {
let mut count = rapid_click_count.borrow_mut();
*count += 1;
if *count == RAPID_CLICK_THRESHOLD {
re_log::warn!(
"Rapid click detected ({} clicks within {}ms)",
RAPID_CLICK_THRESHOLD,
CLICK_DEBOUNCE_MS
);
}
continue;
} else {
*rapid_click_count.borrow_mut() = 0;
}
*last_click_time.borrow_mut() = now;

let timestamp_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;

// Build click event and publish via LCM
let click = click_event_from_ms(
[pos.x, pos.y, pos.z],
&entity_path.to_string(),
timestamp_ms,
);

match lcm_publisher.publish(&click) {
Ok(_) => {
re_log::debug!(
"LCM click event published: entity={}, pos=({:.2}, {:.2}, {:.2})",
entity_path,
pos.x,
pos.y,
pos.z
);
}
Err(err) => {
re_log::error!("Failed to publish LCM click event: {err:?}");
}
}
}
re_viewer::SelectionChangeItem::Entity { position: None, .. } => {
no_position_count += 1;
}
_ => {}
}
}

if !has_position && no_position_count > 0 {
re_log::trace!(
"Selection change without position data ({no_position_count} items). \
This is normal for hover/keyboard navigation."
);
}
}
}
})),
..Default::default()
};

let window_title = "DimOS Interactive Viewer";
eframe::run_native(
window_title,
native_options,
Box::new(move |cc| {
re_viewer::customize_eframe_and_setup_renderer(cc)?;

let mut rerun_app = re_viewer::App::new(
main_thread_token,
re_viewer::build_info(),
app_env,
startup_options,
cc,
None,
re_viewer::AsyncRuntimeHandle::from_current_tokio_runtime_or_wasmbindgen()?,
);

rerun_app.add_log_receiver(rx_log);

Ok(Box::new(rerun_app))
}),
)?;

Ok(())
}
File renamed without changes.
10 changes: 0 additions & 10 deletions examples/rust/custom_callback/.gitignore

This file was deleted.

7 changes: 5 additions & 2 deletions examples/rust/custom_callback/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ license = "MIT OR Apache-2.0"
publish = false

[[bin]]
name = "dimos-viewer"
name = "custom_callback_viewer"
path = "src/viewer.rs"

[[bin]]
name = "custom_callback_app"
path = "src/app.rs"

[features]
default = []

Expand All @@ -23,7 +27,6 @@ rerun = { path = "../../../crates/top/rerun", default-features = false, features
"server",
] }

clap = { workspace = true, features = ["derive"] }
bincode.workspace = true
mimalloc.workspace = true
parking_lot.workspace = true
Expand Down
Loading
Loading