diff --git a/crates/re_data_store/src/store_db.rs b/crates/re_data_store/src/store_db.rs index a54d1242b400..55c4d9ca0738 100644 --- a/crates/re_data_store/src/store_db.rs +++ b/crates/re_data_store/src/store_db.rs @@ -211,7 +211,7 @@ impl EntityDb { // ---------------------------------------------------------------------------- -/// A in-memory database built from a stream of [`LogMsg`]es. +/// An in-memory database built from a stream of [`LogMsg`]es. /// /// NOTE: all mutation is to be done via public functions! pub struct StoreDb { @@ -238,6 +238,27 @@ impl StoreDb { } } + /// Helper function to create a recording from a [`StoreInfo`] and a some [`DataRow`]s. + /// + /// This is useful to programmatically create recordings from within the viewer, which cannot + /// use the `re_sdk`, which is not Wasm-compatible. + pub fn from_info_and_rows( + store_info: StoreInfo, + rows: impl IntoIterator, + ) -> Result { + let mut store_db = StoreDb::new(store_info.store_id.clone()); + + store_db.set_store_info(SetStoreInfo { + row_id: RowId::random(), + info: store_info, + }); + for row in rows { + store_db.add_data_row(&row)?; + } + + Ok(store_db) + } + #[inline] pub fn entity_db(&self) -> &EntityDb { &self.entity_db diff --git a/crates/re_log_types/src/lib.rs b/crates/re_log_types/src/lib.rs index 58dcc6011698..9bbea82d704a 100644 --- a/crates/re_log_types/src/lib.rs +++ b/crates/re_log_types/src/lib.rs @@ -344,6 +344,9 @@ pub enum StoreSource { file_source: FileSource, }, + /// Generated from the viewer itself. + Viewer, + /// Perhaps from some manual data ingestion? Other(String), } @@ -360,6 +363,7 @@ impl std::fmt::Display for StoreSource { FileSource::DragAndDrop => write!(f, "File via drag-and-drop"), FileSource::FileDialog => write!(f, "File via file dialog"), }, + Self::Viewer => write!(f, "Viewer-generated"), Self::Other(string) => format!("{string:?}").fmt(f), // put it in quotes } } diff --git a/crates/re_viewer/data/quick_start_guides/cpp_native.md b/crates/re_viewer/data/quick_start_guides/cpp_native.md new file mode 100644 index 000000000000..6e96eb5cd031 --- /dev/null +++ b/crates/re_viewer/data/quick_start_guides/cpp_native.md @@ -0,0 +1,3 @@ +## C++ Quick Start + +TODO(ab): https://github.com/rerun-io/rerun/issues/3870 diff --git a/crates/re_viewer/data/quick_start_guides/how_does_it_work.md b/crates/re_viewer/data/quick_start_guides/how_does_it_work.md new file mode 100644 index 000000000000..b252be0d1640 --- /dev/null +++ b/crates/re_viewer/data/quick_start_guides/how_does_it_work.md @@ -0,0 +1,9 @@ +### How does it work? + +Rerun's goal is to make handling and visualizing multimodal data streams easy and performant. + +Rerun is made of two main building blocks: the SDK and the Viewer. The data provided by the user code is serialised by the SDK and transferred (via a log file, a TCP socket, a WebSocket, etc.) to the Viewer process for visualization. You can learn more about Rerun's operating modes [here](https://www.rerun.io/docs/reference/sdk-operating-modes). + +In the example above, the SDK connects via a TCP socket to the present viewer. + +The `log()` function logs _entities_ represented by the "entity path" provided as first argument. Entities are a collection of _components_, which hold the actual data such as position, color, or pixel data. _Archetypes_ such as `Points3D` are builder objects which help creating entities with a consistent set of components that are recognized by the Viewer (they can be entirely bypassed when required by advanced use-cases). You can learn more about Rerun's data model [here](https://www.rerun.io/docs/concepts/entity-component). diff --git a/crates/re_viewer/data/quick_start_guides/python_native.md b/crates/re_viewer/data/quick_start_guides/python_native.md new file mode 100644 index 000000000000..6c33bde8ba82 --- /dev/null +++ b/crates/re_viewer/data/quick_start_guides/python_native.md @@ -0,0 +1,33 @@ +## Python Quick Start + +### Installing the Rerun SDK + +The Rerun SDK is available on [PyPI](https://pypi.org/) under the +[`rerun-sdk`](https://pypi.org/project/rerun-sdk/) name. It can be installed like any other +Python package: + +```sh +pip install rerun-sdk +``` + +### Try out the viewer + +The Rerun SDK comes with a demo that can be used to try the viewer. You can send a demo recording +to this viewer using the following command: + +```sh +python -m rerun_sdk.demo --connect +``` + +This will open a new recording that looks like this: + +![Demo recording](https://static.rerun.io/quickstart2_simple_cube/632a8f1c79f70a2355fad294fe085291fcf3a8ae/768w.png) + + +### Logging your own data + +Instead of a pre-packaged demo, you can log your own data. Copy and paste the following snippet in a new Python file and execute it to create a new recording in this viewer: + +```python +${EXAMPLE_CODE} +``` diff --git a/crates/re_viewer/data/quick_start_guides/quick_start_connect.cpp b/crates/re_viewer/data/quick_start_guides/quick_start_connect.cpp new file mode 120000 index 000000000000..2b9e140cfbfc --- /dev/null +++ b/crates/re_viewer/data/quick_start_guides/quick_start_connect.cpp @@ -0,0 +1 @@ +../../../../docs/code-examples/quick_start_connect.cpp \ No newline at end of file diff --git a/crates/re_viewer/data/quick_start_guides/quick_start_connect.py b/crates/re_viewer/data/quick_start_guides/quick_start_connect.py new file mode 120000 index 000000000000..b64a5733fea1 --- /dev/null +++ b/crates/re_viewer/data/quick_start_guides/quick_start_connect.py @@ -0,0 +1 @@ +../../../../docs/code-examples/quick_start_connect.py \ No newline at end of file diff --git a/crates/re_viewer/data/quick_start_guides/quick_start_connect.rs b/crates/re_viewer/data/quick_start_guides/quick_start_connect.rs new file mode 120000 index 000000000000..8458ae676f31 --- /dev/null +++ b/crates/re_viewer/data/quick_start_guides/quick_start_connect.rs @@ -0,0 +1 @@ +../../../../docs/code-examples/quick_start_connect.rs \ No newline at end of file diff --git a/crates/re_viewer/data/quick_start_guides/rust_native.md b/crates/re_viewer/data/quick_start_guides/rust_native.md new file mode 100644 index 000000000000..6fc3b86d96a3 --- /dev/null +++ b/crates/re_viewer/data/quick_start_guides/rust_native.md @@ -0,0 +1,31 @@ +## Rust Quick Start + +### Installing Rerun + +To use the Rerun SDK in your project, you need the [rerun crate](https://crates.io/crates/rerun) which you can add with `cargo add rerun`. + +Let's try it out in a brand-new Rust project: + +```sh +cargo init cube && cd cube && cargo add rerun --features native_viewer +``` + +Note that the Rerun SDK requires a working installation of Rust 1.72+. + +### Logging your own data + +Add the following code to your `main.rs` file: + +```rust +${EXAMPLE_CODE} +``` + +You can now run your application: + +```shell +cargo run +``` + +Once everything finishes compiling, you will see the points in this viewer: + +![Demo recording](https://static.rerun.io/intro_rust_result/cc780eb9bf014d8b1a68fac174b654931f92e14f/768w.png) diff --git a/crates/re_viewer/src/app.rs b/crates/re_viewer/src/app.rs index 05812cd1a194..597fd545ba3a 100644 --- a/crates/re_viewer/src/app.rs +++ b/crates/re_viewer/src/app.rs @@ -349,6 +349,12 @@ impl App { } } + SystemCommand::LoadStoreDb(store_db) => { + let store_id = store_db.store_id().clone(); + store_hub.insert_recording(store_db); + store_hub.set_recording_id(store_id); + } + SystemCommand::ResetViewer => self.reset(store_hub, egui_ctx), SystemCommand::UpdateBlueprint(blueprint_id, updates) => { let blueprint_db = store_hub.store_db_mut(&blueprint_id); diff --git a/crates/re_viewer/src/lib.rs b/crates/re_viewer/src/lib.rs index b2ebcb8d255f..948a0d3151c0 100644 --- a/crates/re_viewer/src/lib.rs +++ b/crates/re_viewer/src/lib.rs @@ -102,7 +102,10 @@ impl AppEnvironment { llvm_version: llvm_version.clone(), }, - StoreSource::File { .. } | StoreSource::Unknown | StoreSource::Other(_) => { + StoreSource::File { .. } + | StoreSource::Unknown + | StoreSource::Viewer + | StoreSource::Other(_) => { // We should not really get here #[cfg(debug_assertions)] diff --git a/crates/re_viewer/src/store_hub.rs b/crates/re_viewer/src/store_hub.rs index 7479886fd67c..ea376ad5d578 100644 --- a/crates/re_viewer/src/store_hub.rs +++ b/crates/re_viewer/src/store_hub.rs @@ -192,6 +192,14 @@ impl StoreHub { } } + /// Insert a new recording into the [`StoreHub`]. + /// + /// Note that the recording is not automatically made active. Use [`StoreHub::set_recording_id`] + /// if needed. + pub fn insert_recording(&mut self, store_db: StoreDb) { + self.store_dbs.insert_recording(store_db); + } + /// Mutable access to a [`StoreDb`] by id pub fn store_db_mut(&mut self, store_id: &StoreId) -> &mut StoreDb { self.store_dbs.store_db_entry(store_id) diff --git a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs index 8b5920b61962..cdab927e19a1 100644 --- a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs +++ b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs @@ -1,12 +1,14 @@ use super::{large_text_button, status_strings, url_large_text_button, WelcomeScreenResponse}; use egui::{NumExt, Ui}; -use re_log_types::LogMsg; +use itertools::Itertools; +use re_data_store::StoreDb; +use re_log_types::{ + DataRow, EntityPath, LogMsg, RowId, StoreId, StoreInfo, StoreKind, StoreSource, Time, TimePoint, +}; use re_smart_channel::ReceiveSet; use re_ui::UICommandSender; +use re_viewer_context::{SystemCommand, SystemCommandSender}; -//const CPP_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/cpp"; -const PYTHON_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/python"; -const RUST_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/rust"; const SPACE_VIEWS_HELP: &str = "https://www.rerun.io/docs/getting-started/viewer-walkthrough"; /// Show the welcome page. @@ -58,10 +60,50 @@ fn onboarding_content_ui( Visualize synchronized data from multiple processes, locally or over a network.", image: &re_ui::icons::WELCOME_SCREEN_LIVE_DATA, add_buttons: Box::new(|ui: &mut egui::Ui| { - // TODO(ab): activate when C++ is ready! - // url_large_text_button(ui, "C++", CPP_QUICKSTART); - url_large_text_button(ui, "Python", PYTHON_QUICKSTART); - url_large_text_button(ui, "Rust", RUST_QUICKSTART); + //TODO(#3870): enable with C++ guides are completed + #[allow(clippy::collapsible_if)] + if false { + if large_text_button(ui, "C++").clicked() { + open_quick_start( + command_sender, + [ + include_str!("../../../data/quick_start_guides/cpp_native.md"), + include_str!( + "../../../data/quick_start_guides/how_does_it_work.md" + ), + ], + include_str!( + "../../../data/quick_start_guides/quick_start_connect.cpp" + ), + "C++ Quick Start", + "cpp_quick_start", + ); + } + } + if large_text_button(ui, "Python").clicked() { + open_quick_start( + command_sender, + [ + include_str!("../../../data/quick_start_guides/python_native.md"), + include_str!("../../../data/quick_start_guides/how_does_it_work.md"), + ], + include_str!("../../../data/quick_start_guides/quick_start_connect.py"), + "Python Quick Start", + "python_quick_start", + ); + } + if large_text_button(ui, "Rust").clicked() { + open_quick_start( + command_sender, + [ + include_str!("../../../data/quick_start_guides/rust_native.md"), + include_str!("../../../data/quick_start_guides/how_does_it_work.md"), + ], + include_str!("../../../data/quick_start_guides/quick_start_connect.rs"), + "Rust Quick Start", + "rust_quick_start", + ); + } false }), @@ -102,7 +144,7 @@ fn onboarding_content_ui( }, ]; - // Shrink images if needed so user can see all of the content buttons + // Shrink images if needed so user can see all the content buttons let max_image_height = ui.available_height() - 300.0; let centering_vspace = (ui.available_height() - 650.0) / 2.0; @@ -231,3 +273,54 @@ fn image_banner(ui: &mut egui::Ui, icon: &re_ui::Icon, column_width: f32, max_im ); }); } + +/// Open a Quick Start recording +/// +/// The `parts` are joined with newlines to form the markdown, and the spacial tag +/// `"${EXAMPLE_CODE}"` is replaced with the content of th `example_code` variable. +fn open_quick_start<'a>( + command_sender: &re_viewer_context::CommandSender, + parts: impl IntoIterator, + example_code: &str, + app_id: &str, + entity_path: &str, +) { + let mut markdown = parts.into_iter().join("\n"); + markdown = markdown.replace("${EXAMPLE_CODE}", example_code); + + let res = open_markdown_recording(command_sender, markdown.as_str(), app_id, entity_path); + if let Err(err) = res { + re_log::error!("Failed to load quick start: {}", err); + } +} + +fn open_markdown_recording( + command_sender: &re_viewer_context::CommandSender, + markdown: &str, + app_id: &str, + entity_path: &str, +) -> anyhow::Result<()> { + let text_doc = re_types::archetypes::TextDocument::new(markdown) + .with_media_type(re_types::components::MediaType::markdown()); + + let row = DataRow::from_archetype( + RowId::random(), + TimePoint::timeless(), + EntityPath::from(entity_path), + &text_doc, + )?; + + let store_info = StoreInfo { + application_id: app_id.into(), + store_id: StoreId::random(StoreKind::Recording), + is_official_example: true, + started: Time::now(), + store_source: StoreSource::Viewer, + store_kind: StoreKind::Recording, + }; + + let store_db = StoreDb::from_info_and_rows(store_info, [row])?; + command_sender.send_system(SystemCommand::LoadStoreDb(store_db)); + + Ok(()) +} diff --git a/crates/re_viewer/src/viewer_analytics.rs b/crates/re_viewer/src/viewer_analytics.rs index 4a8a280afba9..9317318e7ae0 100644 --- a/crates/re_viewer/src/viewer_analytics.rs +++ b/crates/re_viewer/src/viewer_analytics.rs @@ -181,6 +181,7 @@ impl ViewerAnalytics { re_log_types::FileSource::DragAndDrop => "file_drag_and_drop".to_owned(), re_log_types::FileSource::FileDialog => "file_dialog".to_owned(), }, + StoreSource::Viewer => "viewer".to_owned(), StoreSource::Other(other) => other.clone(), }; @@ -210,7 +211,7 @@ impl ViewerAnalytics { self.deregister("llvm_version"); // can't be both! } StoreSource::CSdk => {} // TODO(andreas): Send version and set it. - StoreSource::Unknown | StoreSource::Other(_) => {} + StoreSource::Unknown | StoreSource::Viewer | StoreSource::Other(_) => {} } self.register("store_source", store_source); diff --git a/crates/re_viewer_context/src/command_sender.rs b/crates/re_viewer_context/src/command_sender.rs index c3923a6f3b4d..4de6ac5ed72a 100644 --- a/crates/re_viewer_context/src/command_sender.rs +++ b/crates/re_viewer_context/src/command_sender.rs @@ -1,4 +1,5 @@ use re_data_source::DataSource; +use re_data_store::StoreDb; use re_log_types::{DataRow, StoreId}; use re_ui::{UICommand, UICommandSender}; @@ -10,6 +11,9 @@ pub enum SystemCommand { /// Load some data. LoadDataSource(DataSource), + /// Load some log messages. + LoadStoreDb(StoreDb), + /// Reset the `Viewer` to the default state ResetViewer, diff --git a/docs/code-examples/Cargo.toml b/docs/code-examples/Cargo.toml index a96c166ab052..33a3f2c7b139 100644 --- a/docs/code-examples/Cargo.toml +++ b/docs/code-examples/Cargo.toml @@ -35,6 +35,10 @@ path = "asset3d_simple.rs" name = "asset3d_out_of_tree" path = "asset3d_out_of_tree.rs" +[[bin]] +name = "box2d_simple" +path = "box2d_simple.rs" + [[bin]] name = "box3d_simple" path = "box3d_simple.rs" @@ -132,8 +136,8 @@ name = "point3d_simple" path = "point3d_simple.rs" [[bin]] -name = "box2d_simple" -path = "box2d_simple.rs" +name = "quick_start_connect" +path = "quick_start_connect.rs" [[bin]] name = "scalar_simple" diff --git a/docs/code-examples/quick_start_connect.cpp b/docs/code-examples/quick_start_connect.cpp new file mode 100644 index 000000000000..e8c7c8edccd5 --- /dev/null +++ b/docs/code-examples/quick_start_connect.cpp @@ -0,0 +1,5 @@ +// TODO(ab): https://github.com/rerun-io/rerun/issues/3870 + +int main() { + return 0; +} diff --git a/docs/code-examples/quick_start_connect.py b/docs/code-examples/quick_start_connect.py new file mode 100644 index 000000000000..543fbc4fe813 --- /dev/null +++ b/docs/code-examples/quick_start_connect.py @@ -0,0 +1,28 @@ +"""Connect to the viewer and log some data.""" + +import numpy as np +import rerun as rr + +# Initialize the SDK and give our recording a unique name +rr.init("rerun_example_demo") + +# Connect to a local viewer using the default port +rr.connect() + + +# Create some data +SIZE = 10 + +pos_grid = np.meshgrid(*[np.linspace(-10, 10, SIZE)] * 3) +positions = np.vstack([d.reshape(-1) for d in pos_grid]).T + +col_grid = np.meshgrid(*[np.linspace(0, 255, SIZE)] * 3) +colors = np.vstack([c.reshape(-1) for c in col_grid]).astype(np.uint8).T + +# Log the data +rr.log( + # name under which this entity is logged (known as "entity path") + "my_points", + # log data as a 3D point cloud archetype + rr.Points3D(positions, colors=colors, radii=0.5), +) diff --git a/docs/code-examples/quick_start_connect.rs b/docs/code-examples/quick_start_connect.rs new file mode 100644 index 000000000000..e5f4665a6609 --- /dev/null +++ b/docs/code-examples/quick_start_connect.rs @@ -0,0 +1,24 @@ +//! Connect to the viewer and log some data. + +use rerun::{demo_util::grid, external::glam}; + +fn main() -> Result<(), Box> { + // Create a new `RecordingStream` which sends data over TCP to the viewer process. + let rec = rerun::RecordingStreamBuilder::new("rerun_example_demo") + .connect("127.0.0.1:9876".parse()?, None)?; + + // Create some data using the `grid` utility function. + let points = grid(glam::Vec3::splat(-10.0), glam::Vec3::splat(10.0), 10); + let colors = grid(glam::Vec3::ZERO, glam::Vec3::splat(255.0), 10) + .map(|v| rerun::Color::from_rgb(v.x as u8, v.y as u8, v.z as u8)); + + // Log the "my_points" entity with our data, using the `Points3D` archetype. + rec.log( + "my_points", + &rerun::Points3D::new(points) + .with_colors(colors) + .with_radii([0.5]), + )?; + + Ok(()) +} diff --git a/docs/code-examples/roundtrips.py b/docs/code-examples/roundtrips.py index 13328bb0ad3c..4d74c1fc769c 100755 --- a/docs/code-examples/roundtrips.py +++ b/docs/code-examples/roundtrips.py @@ -33,6 +33,7 @@ "image_simple": ["cpp"], # TODO(#2919): Not yet implemented in C++ "log_line": ["cpp", "rust", "py"], # Not a complete example -- just a single log line "pinhole_simple": ["cpp"], # TODO(#2919): Seg-faults in C++ + "quick_start_connect": ["cpp"], # TODO(#3870): Not yet implemented in C++ "scalar_multiple_plots": ["cpp"], # TODO(#2919): Not yet implemented in C++ "segmentation_image_simple": ["cpp"], # TODO(#2919): Not yet implemented in C++ "tensor_simple": ["cpp"], # TODO(#2919): Not yet implemented in C++ @@ -53,6 +54,7 @@ "pinhole_simple": ["cpp", "py", "rust"], # TODO(#3206): examples use different RNGs "point2d_random": ["cpp", "py", "rust"], # TODO(#3206): examples use different RNGs "point3d_random": ["cpp", "py", "rust"], # TODO(#3206): examples use different RNGs + "quick_start_connect": ["cpp", "py", "rust"], # These example don't have exactly the same implementation. "tensor_simple": ["cpp", "py", "rust"], # TODO(#3206): examples use different RNGs "transform3d_simple": ["cpp"], # TODO(#2919): Something broken in the C++ SDK }